summaryrefslogtreecommitdiff
path: root/vendor/symfony/string
diff options
context:
space:
mode:
authorpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
committerpolo <ordipolo@gmx.fr>2024-08-13 23:45:21 +0200
commitbf6655a534a6775d30cafa67bd801276bda1d98d (patch)
treec6381e3f6c81c33eab72508f410b165ba05f7e9c /vendor/symfony/string
parent94d67a4b51f8e62e7d518cce26a526ae1ec48278 (diff)
downloadAppliGestionPHP-bf6655a534a6775d30cafa67bd801276bda1d98d.zip
VERSION 0.2 doctrine ORM et entités
Diffstat (limited to 'vendor/symfony/string')
-rw-r--r--vendor/symfony/string/AbstractString.php702
-rw-r--r--vendor/symfony/string/AbstractUnicodeString.php664
-rw-r--r--vendor/symfony/string/ByteString.php490
-rw-r--r--vendor/symfony/string/CHANGELOG.md45
-rw-r--r--vendor/symfony/string/CodePointString.php260
-rw-r--r--vendor/symfony/string/Exception/ExceptionInterface.php16
-rw-r--r--vendor/symfony/string/Exception/InvalidArgumentException.php16
-rw-r--r--vendor/symfony/string/Exception/RuntimeException.php16
-rw-r--r--vendor/symfony/string/Inflector/EnglishInflector.php577
-rw-r--r--vendor/symfony/string/Inflector/FrenchInflector.php151
-rw-r--r--vendor/symfony/string/Inflector/InflectorInterface.php33
-rw-r--r--vendor/symfony/string/LICENSE19
-rw-r--r--vendor/symfony/string/LazyString.php145
-rw-r--r--vendor/symfony/string/README.md14
-rw-r--r--vendor/symfony/string/Resources/data/wcswidth_table_wide.php1155
-rw-r--r--vendor/symfony/string/Resources/data/wcswidth_table_zero.php1415
-rw-r--r--vendor/symfony/string/Resources/functions.php38
-rw-r--r--vendor/symfony/string/Slugger/AsciiSlugger.php207
-rw-r--r--vendor/symfony/string/Slugger/SluggerInterface.php27
-rw-r--r--vendor/symfony/string/UnicodeString.php382
-rw-r--r--vendor/symfony/string/composer.json44
21 files changed, 6416 insertions, 0 deletions
diff --git a/vendor/symfony/string/AbstractString.php b/vendor/symfony/string/AbstractString.php
new file mode 100644
index 0000000..253d2dc
--- /dev/null
+++ b/vendor/symfony/string/AbstractString.php
@@ -0,0 +1,702 @@
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
12namespace Symfony\Component\String;
13
14use Symfony\Component\String\Exception\ExceptionInterface;
15use Symfony\Component\String\Exception\InvalidArgumentException;
16use Symfony\Component\String\Exception\RuntimeException;
17
18/**
19 * Represents a string of abstract characters.
20 *
21 * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
22 * This class is the abstract type to use as a type-hint when the logic you want to
23 * implement doesn't care about the exact variant it deals with.
24 *
25 * @author Nicolas Grekas <p@tchwork.com>
26 * @author Hugo Hamon <hugohamon@neuf.fr>
27 *
28 * @throws ExceptionInterface
29 */
30abstract class AbstractString implements \Stringable, \JsonSerializable
31{
32 public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
33 public const PREG_SET_ORDER = \PREG_SET_ORDER;
34 public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
35 public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;
36
37 public const PREG_SPLIT = 0;
38 public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
39 public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
40 public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;
41
42 protected string $string = '';
43 protected ?bool $ignoreCase = false;
44
45 abstract public function __construct(string $string = '');
46
47 /**
48 * Unwraps instances of AbstractString back to strings.
49 *
50 * @return string[]|array
51 */
52 public static function unwrap(array $values): array
53 {
54 foreach ($values as $k => $v) {
55 if ($v instanceof self) {
56 $values[$k] = $v->__toString();
57 } elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) {
58 $values[$k] = $v;
59 }
60 }
61
62 return $values;
63 }
64
65 /**
66 * Wraps (and normalizes) strings in instances of AbstractString.
67 *
68 * @return static[]|array
69 */
70 public static function wrap(array $values): array
71 {
72 $i = 0;
73 $keys = null;
74
75 foreach ($values as $k => $v) {
76 if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
77 $keys ??= array_keys($values);
78 $keys[$i] = $j;
79 }
80
81 if (\is_string($v)) {
82 $values[$k] = new static($v);
83 } elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) {
84 $values[$k] = $v;
85 }
86
87 ++$i;
88 }
89
90 return null !== $keys ? array_combine($keys, $values) : $values;
91 }
92
93 /**
94 * @param string|string[] $needle
95 */
96 public function after(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
97 {
98 $str = clone $this;
99 $i = \PHP_INT_MAX;
100
101 if (\is_string($needle)) {
102 $needle = [$needle];
103 }
104
105 foreach ($needle as $n) {
106 $n = (string) $n;
107 $j = $this->indexOf($n, $offset);
108
109 if (null !== $j && $j < $i) {
110 $i = $j;
111 $str->string = $n;
112 }
113 }
114
115 if (\PHP_INT_MAX === $i) {
116 return $str;
117 }
118
119 if (!$includeNeedle) {
120 $i += $str->length();
121 }
122
123 return $this->slice($i);
124 }
125
126 /**
127 * @param string|string[] $needle
128 */
129 public function afterLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
130 {
131 $str = clone $this;
132 $i = null;
133
134 if (\is_string($needle)) {
135 $needle = [$needle];
136 }
137
138 foreach ($needle as $n) {
139 $n = (string) $n;
140 $j = $this->indexOfLast($n, $offset);
141
142 if (null !== $j && $j >= $i) {
143 $i = $offset = $j;
144 $str->string = $n;
145 }
146 }
147
148 if (null === $i) {
149 return $str;
150 }
151
152 if (!$includeNeedle) {
153 $i += $str->length();
154 }
155
156 return $this->slice($i);
157 }
158
159 abstract public function append(string ...$suffix): static;
160
161 /**
162 * @param string|string[] $needle
163 */
164 public function before(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
165 {
166 $str = clone $this;
167 $i = \PHP_INT_MAX;
168
169 if (\is_string($needle)) {
170 $needle = [$needle];
171 }
172
173 foreach ($needle as $n) {
174 $n = (string) $n;
175 $j = $this->indexOf($n, $offset);
176
177 if (null !== $j && $j < $i) {
178 $i = $j;
179 $str->string = $n;
180 }
181 }
182
183 if (\PHP_INT_MAX === $i) {
184 return $str;
185 }
186
187 if ($includeNeedle) {
188 $i += $str->length();
189 }
190
191 return $this->slice(0, $i);
192 }
193
194 /**
195 * @param string|string[] $needle
196 */
197 public function beforeLast(string|iterable $needle, bool $includeNeedle = false, int $offset = 0): static
198 {
199 $str = clone $this;
200 $i = null;
201
202 if (\is_string($needle)) {
203 $needle = [$needle];
204 }
205
206 foreach ($needle as $n) {
207 $n = (string) $n;
208 $j = $this->indexOfLast($n, $offset);
209
210 if (null !== $j && $j >= $i) {
211 $i = $offset = $j;
212 $str->string = $n;
213 }
214 }
215
216 if (null === $i) {
217 return $str;
218 }
219
220 if ($includeNeedle) {
221 $i += $str->length();
222 }
223
224 return $this->slice(0, $i);
225 }
226
227 /**
228 * @return int[]
229 */
230 public function bytesAt(int $offset): array
231 {
232 $str = $this->slice($offset, 1);
233
234 return '' === $str->string ? [] : array_values(unpack('C*', $str->string));
235 }
236
237 abstract public function camel(): static;
238
239 /**
240 * @return static[]
241 */
242 abstract public function chunk(int $length = 1): array;
243
244 public function collapseWhitespace(): static
245 {
246 $str = clone $this;
247 $str->string = trim(preg_replace("/(?:[ \n\r\t\x0C]{2,}+|[\n\r\t\x0C])/", ' ', $str->string), " \n\r\t\x0C");
248
249 return $str;
250 }
251
252 /**
253 * @param string|string[] $needle
254 */
255 public function containsAny(string|iterable $needle): bool
256 {
257 return null !== $this->indexOf($needle);
258 }
259
260 /**
261 * @param string|string[] $suffix
262 */
263 public function endsWith(string|iterable $suffix): bool
264 {
265 if (\is_string($suffix)) {
266 throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
267 }
268
269 foreach ($suffix as $s) {
270 if ($this->endsWith((string) $s)) {
271 return true;
272 }
273 }
274
275 return false;
276 }
277
278 public function ensureEnd(string $suffix): static
279 {
280 if (!$this->endsWith($suffix)) {
281 return $this->append($suffix);
282 }
283
284 $suffix = preg_quote($suffix);
285 $regex = '{('.$suffix.')(?:'.$suffix.')++$}D';
286
287 return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
288 }
289
290 public function ensureStart(string $prefix): static
291 {
292 $prefix = new static($prefix);
293
294 if (!$this->startsWith($prefix)) {
295 return $this->prepend($prefix);
296 }
297
298 $str = clone $this;
299 $i = $prefixLen = $prefix->length();
300
301 while ($this->indexOf($prefix, $i) === $i) {
302 $str = $str->slice($prefixLen);
303 $i += $prefixLen;
304 }
305
306 return $str;
307 }
308
309 /**
310 * @param string|string[] $string
311 */
312 public function equalsTo(string|iterable $string): bool
313 {
314 if (\is_string($string)) {
315 throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
316 }
317
318 foreach ($string as $s) {
319 if ($this->equalsTo((string) $s)) {
320 return true;
321 }
322 }
323
324 return false;
325 }
326
327 abstract public function folded(): static;
328
329 public function ignoreCase(): static
330 {
331 $str = clone $this;
332 $str->ignoreCase = true;
333
334 return $str;
335 }
336
337 /**
338 * @param string|string[] $needle
339 */
340 public function indexOf(string|iterable $needle, int $offset = 0): ?int
341 {
342 if (\is_string($needle)) {
343 throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
344 }
345
346 $i = \PHP_INT_MAX;
347
348 foreach ($needle as $n) {
349 $j = $this->indexOf((string) $n, $offset);
350
351 if (null !== $j && $j < $i) {
352 $i = $j;
353 }
354 }
355
356 return \PHP_INT_MAX === $i ? null : $i;
357 }
358
359 /**
360 * @param string|string[] $needle
361 */
362 public function indexOfLast(string|iterable $needle, int $offset = 0): ?int
363 {
364 if (\is_string($needle)) {
365 throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
366 }
367
368 $i = null;
369
370 foreach ($needle as $n) {
371 $j = $this->indexOfLast((string) $n, $offset);
372
373 if (null !== $j && $j >= $i) {
374 $i = $offset = $j;
375 }
376 }
377
378 return $i;
379 }
380
381 public function isEmpty(): bool
382 {
383 return '' === $this->string;
384 }
385
386 abstract public function join(array $strings, ?string $lastGlue = null): static;
387
388 public function jsonSerialize(): string
389 {
390 return $this->string;
391 }
392
393 abstract public function length(): int;
394
395 abstract public function lower(): static;
396
397 /**
398 * Matches the string using a regular expression.
399 *
400 * Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression.
401 *
402 * @return array All matches in a multi-dimensional array ordered according to flags
403 */
404 abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array;
405
406 abstract public function padBoth(int $length, string $padStr = ' '): static;
407
408 abstract public function padEnd(int $length, string $padStr = ' '): static;
409
410 abstract public function padStart(int $length, string $padStr = ' '): static;
411
412 abstract public function prepend(string ...$prefix): static;
413
414 public function repeat(int $multiplier): static
415 {
416 if (0 > $multiplier) {
417 throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier));
418 }
419
420 $str = clone $this;
421 $str->string = str_repeat($str->string, $multiplier);
422
423 return $str;
424 }
425
426 abstract public function replace(string $from, string $to): static;
427
428 abstract public function replaceMatches(string $fromRegexp, string|callable $to): static;
429
430 abstract public function reverse(): static;
431
432 abstract public function slice(int $start = 0, ?int $length = null): static;
433
434 abstract public function snake(): static;
435
436 abstract public function splice(string $replacement, int $start = 0, ?int $length = null): static;
437
438 /**
439 * @return static[]
440 */
441 public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
442 {
443 if (null === $flags) {
444 throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
445 }
446
447 if ($this->ignoreCase) {
448 $delimiter .= 'i';
449 }
450
451 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
452
453 try {
454 if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
455 throw new RuntimeException('Splitting failed with error: '.preg_last_error_msg());
456 }
457 } finally {
458 restore_error_handler();
459 }
460
461 $str = clone $this;
462
463 if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) {
464 foreach ($chunks as &$chunk) {
465 $str->string = $chunk[0];
466 $chunk[0] = clone $str;
467 }
468 } else {
469 foreach ($chunks as &$chunk) {
470 $str->string = $chunk;
471 $chunk = clone $str;
472 }
473 }
474
475 return $chunks;
476 }
477
478 /**
479 * @param string|string[] $prefix
480 */
481 public function startsWith(string|iterable $prefix): bool
482 {
483 if (\is_string($prefix)) {
484 throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
485 }
486
487 foreach ($prefix as $prefix) {
488 if ($this->startsWith((string) $prefix)) {
489 return true;
490 }
491 }
492
493 return false;
494 }
495
496 abstract public function title(bool $allWords = false): static;
497
498 public function toByteString(?string $toEncoding = null): ByteString
499 {
500 $b = new ByteString();
501
502 $toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding;
503
504 if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') {
505 $b->string = $this->string;
506
507 return $b;
508 }
509
510 try {
511 $b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
512 } catch (\ValueError $e) {
513 if (!\function_exists('iconv')) {
514 throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
515 }
516
517 $b->string = iconv('UTF-8', $toEncoding, $this->string);
518 }
519
520 return $b;
521 }
522
523 public function toCodePointString(): CodePointString
524 {
525 return new CodePointString($this->string);
526 }
527
528 public function toString(): string
529 {
530 return $this->string;
531 }
532
533 public function toUnicodeString(): UnicodeString
534 {
535 return new UnicodeString($this->string);
536 }
537
538 abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;
539
540 abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;
541
542 /**
543 * @param string|string[] $prefix
544 */
545 public function trimPrefix($prefix): static
546 {
547 if (\is_array($prefix) || $prefix instanceof \Traversable) { // don't use is_iterable(), it's slow
548 foreach ($prefix as $s) {
549 $t = $this->trimPrefix($s);
550
551 if ($t->string !== $this->string) {
552 return $t;
553 }
554 }
555
556 return clone $this;
557 }
558
559 $str = clone $this;
560
561 if ($prefix instanceof self) {
562 $prefix = $prefix->string;
563 } else {
564 $prefix = (string) $prefix;
565 }
566
567 if ('' !== $prefix && \strlen($this->string) >= \strlen($prefix) && 0 === substr_compare($this->string, $prefix, 0, \strlen($prefix), $this->ignoreCase)) {
568 $str->string = substr($this->string, \strlen($prefix));
569 }
570
571 return $str;
572 }
573
574 abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static;
575
576 /**
577 * @param string|string[] $suffix
578 */
579 public function trimSuffix($suffix): static
580 {
581 if (\is_array($suffix) || $suffix instanceof \Traversable) { // don't use is_iterable(), it's slow
582 foreach ($suffix as $s) {
583 $t = $this->trimSuffix($s);
584
585 if ($t->string !== $this->string) {
586 return $t;
587 }
588 }
589
590 return clone $this;
591 }
592
593 $str = clone $this;
594
595 if ($suffix instanceof self) {
596 $suffix = $suffix->string;
597 } else {
598 $suffix = (string) $suffix;
599 }
600
601 if ('' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase)) {
602 $str->string = substr($this->string, 0, -\strlen($suffix));
603 }
604
605 return $str;
606 }
607
608 public function truncate(int $length, string $ellipsis = '', bool $cut = true): static
609 {
610 $stringLength = $this->length();
611
612 if ($stringLength <= $length) {
613 return clone $this;
614 }
615
616 $ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0;
617
618 if ($length < $ellipsisLength) {
619 $ellipsisLength = 0;
620 }
621
622 if (!$cut) {
623 if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) {
624 return clone $this;
625 }
626
627 $length += $ellipsisLength;
628 }
629
630 $str = $this->slice(0, $length - $ellipsisLength);
631
632 return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str;
633 }
634
635 abstract public function upper(): static;
636
637 /**
638 * Returns the printable length on a terminal.
639 */
640 abstract public function width(bool $ignoreAnsiDecoration = true): int;
641
642 public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): static
643 {
644 $lines = '' !== $break ? $this->split($break) : [clone $this];
645 $chars = [];
646 $mask = '';
647
648 if (1 === \count($lines) && '' === $lines[0]->string) {
649 return $lines[0];
650 }
651
652 foreach ($lines as $i => $line) {
653 if ($i) {
654 $chars[] = $break;
655 $mask .= '#';
656 }
657
658 foreach ($line->chunk() as $char) {
659 $chars[] = $char->string;
660 $mask .= ' ' === $char->string ? ' ' : '?';
661 }
662 }
663
664 $string = '';
665 $j = 0;
666 $b = $i = -1;
667 $mask = wordwrap($mask, $width, '#', $cut);
668
669 while (false !== $b = strpos($mask, '#', $b + 1)) {
670 for (++$i; $i < $b; ++$i) {
671 $string .= $chars[$j];
672 unset($chars[$j++]);
673 }
674
675 if ($break === $chars[$j] || ' ' === $chars[$j]) {
676 unset($chars[$j++]);
677 }
678
679 $string .= $break;
680 }
681
682 $str = clone $this;
683 $str->string = $string.implode('', $chars);
684
685 return $str;
686 }
687
688 public function __sleep(): array
689 {
690 return ['string'];
691 }
692
693 public function __clone()
694 {
695 $this->ignoreCase = false;
696 }
697
698 public function __toString(): string
699 {
700 return $this->string;
701 }
702}
diff --git a/vendor/symfony/string/AbstractUnicodeString.php b/vendor/symfony/string/AbstractUnicodeString.php
new file mode 100644
index 0000000..2cb2917
--- /dev/null
+++ b/vendor/symfony/string/AbstractUnicodeString.php
@@ -0,0 +1,664 @@
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
12namespace Symfony\Component\String;
13
14use Symfony\Component\String\Exception\ExceptionInterface;
15use Symfony\Component\String\Exception\InvalidArgumentException;
16use Symfony\Component\String\Exception\RuntimeException;
17
18/**
19 * Represents a string of abstract Unicode characters.
20 *
21 * Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
22 * This class is the abstract type to use as a type-hint when the logic you want to
23 * implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
24 *
25 * @author Nicolas Grekas <p@tchwork.com>
26 *
27 * @throws ExceptionInterface
28 */
29abstract class AbstractUnicodeString extends AbstractString
30{
31 public const NFC = \Normalizer::NFC;
32 public const NFD = \Normalizer::NFD;
33 public const NFKC = \Normalizer::NFKC;
34 public const NFKD = \Normalizer::NFKD;
35
36 // all ASCII letters sorted by typical frequency of occurrence
37 private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
38
39 // the subset of folded case mappings that is not in lower case mappings
40 private const FOLD_FROM = ['İ', 'µ', 'ſ', "\xCD\x85", 'ς', 'ϐ', 'ϑ', 'ϕ', 'ϖ', 'ϰ', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'և', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ᾀ', 'ᾁ', 'ᾂ', 'ᾃ', 'ᾄ', 'ᾅ', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'ᾍ', 'ᾎ', 'ᾏ', 'ᾐ', 'ᾑ', 'ᾒ', 'ᾓ', 'ᾔ', 'ᾕ', 'ᾖ', 'ᾗ', 'ᾘ', 'ᾙ', 'ᾚ', 'ᾛ', 'ᾜ', 'ᾝ', 'ᾞ', 'ᾟ', 'ᾠ', 'ᾡ', 'ᾢ', 'ᾣ', 'ᾤ', 'ᾥ', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'ᾭ', 'ᾮ', 'ᾯ', 'ᾲ', 'ᾳ', 'ᾴ', 'ᾶ', 'ᾷ', 'ᾼ', 'ῂ', 'ῃ', 'ῄ', 'ῆ', 'ῇ', 'ῌ', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'ῴ', 'ῶ', 'ῷ', 'ῼ', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
41 private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', 'ṡ', 'ι', 'ss', 'ʼn', 'ǰ', 'ΐ', 'ΰ', 'եւ', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'ὐ', 'ὒ', 'ὔ', 'ὖ', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'ἁι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'ῒ', 'ῖ', 'ῗ', 'ῢ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'մն', 'մե', 'մի', 'վն', 'մխ'];
42
43 // the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
44 private const TRANSLIT_FROM = ['Æ', 'Ð', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Đ', 'đ', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ŀ', 'ŀ', 'Ł', 'ł', 'ʼn', 'Ŋ', 'ŋ', 'Œ', 'œ', 'Ŧ', 'ŧ', 'ƀ', 'Ɓ', 'Ƃ', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'Ɗ', 'Ƌ', 'ƌ', 'Ɛ', 'Ƒ', 'ƒ', 'Ɠ', 'ƕ', 'Ɩ', 'Ɨ', 'Ƙ', 'ƙ', 'ƚ', 'Ɲ', 'ƞ', 'Ƣ', 'ƣ', 'Ƥ', 'ƥ', 'ƫ', 'Ƭ', 'ƭ', 'Ʈ', 'Ʋ', 'Ƴ', 'ƴ', 'Ƶ', 'ƶ', 'DŽ', 'Dž', 'dž', 'Ǥ', 'ǥ', 'ȡ', 'Ȥ', 'ȥ', 'ȴ', 'ȵ', 'ȶ', 'ȷ', 'ȸ', 'ȹ', 'Ⱥ', 'Ȼ', 'ȼ', 'Ƚ', 'Ⱦ', 'ȿ', 'ɀ', 'Ƀ', 'Ʉ', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'Ɍ', 'ɍ', 'Ɏ', 'ɏ', 'ɓ', 'ɕ', 'ɖ', 'ɗ', 'ɛ', 'ɟ', 'ɠ', 'ɡ', 'ɢ', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'ɫ', 'ɬ', 'ɭ', 'ɱ', 'ɲ', 'ɳ', 'ɴ', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'ʀ', 'ʂ', 'ʈ', 'ʉ', 'ʋ', 'ʏ', 'ʐ', 'ʑ', 'ʙ', 'ʛ', 'ʜ', 'ʝ', 'ʟ', 'ʠ', 'ʣ', 'ʥ', 'ʦ', 'ʪ', 'ʫ', 'ᴀ', 'ᴁ', 'ᴃ', 'ᴄ', 'ᴅ', 'ᴆ', 'ᴇ', 'ᴊ', 'ᴋ', 'ᴌ', 'ᴍ', 'ᴏ', 'ᴘ', 'ᴛ', 'ᴜ', 'ᴠ', 'ᴡ', 'ᴢ', 'ᵫ', 'ᵬ', 'ᵭ', 'ᵮ', 'ᵯ', 'ᵰ', 'ᵱ', 'ᵲ', 'ᵳ', 'ᵴ', 'ᵵ', 'ᵶ', 'ᵺ', 'ᵻ', 'ᵽ', 'ᵾ', 'ᶀ', 'ᶁ', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'ᶍ', 'ᶎ', 'ᶏ', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'ẝ', 'ẞ', 'Ỻ', 'ỻ', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', '₠', '₢', '₣', '₤', '₧', '₺', '₹', 'ℌ', '℞', '㎧', '㎮', '㏆', '㏗', '㏞', '㏟', '¼', '½', '¾', '⅓', '⅔', '⅕', '⅖', '⅗', '⅘', '⅙', '⅚', '⅛', '⅜', '⅝', '⅞', '⅟', '〇', '‘', '’', '‚', '‛', '“', '”', '„', '‟', '′', '″', '〝', '〞', '«', '»', '‹', '›', '‐', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', '⁄', '⁅', '⁆', '⁎', '、', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', '﹀', '﹑', '﹝', '﹞', '⦅', '⦆', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
45 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', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
46
47 private static array $transliterators = [];
48 private static array $tableZero;
49 private static array $tableWide;
50
51 public static function fromCodePoints(int ...$codes): static
52 {
53 $string = '';
54
55 foreach ($codes as $code) {
56 if (0x80 > $code %= 0x200000) {
57 $string .= \chr($code);
58 } elseif (0x800 > $code) {
59 $string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
60 } elseif (0x10000 > $code) {
61 $string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
62 } else {
63 $string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
64 }
65 }
66
67 return new static($string);
68 }
69
70 /**
71 * Generic UTF-8 to ASCII transliteration.
72 *
73 * Install the intl extension for best results.
74 *
75 * @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
76 */
77 public function ascii(array $rules = []): self
78 {
79 $str = clone $this;
80 $s = $str->string;
81 $str->string = '';
82
83 array_unshift($rules, 'nfd');
84 $rules[] = 'latin-ascii';
85
86 if (\function_exists('transliterator_transliterate')) {
87 $rules[] = 'any-latin/bgn';
88 }
89
90 $rules[] = 'nfkd';
91 $rules[] = '[:nonspacing mark:] remove';
92
93 while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
94 if (0 < --$i) {
95 $str->string .= substr($s, 0, $i);
96 $s = substr($s, $i);
97 }
98
99 if (!$rule = array_shift($rules)) {
100 $rules = []; // An empty rule interrupts the next ones
101 }
102
103 if ($rule instanceof \Transliterator) {
104 $s = $rule->transliterate($s);
105 } elseif ($rule instanceof \Closure) {
106 $s = $rule($s);
107 } elseif ($rule) {
108 if ('nfd' === $rule = strtolower($rule)) {
109 normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD);
110 } elseif ('nfkd' === $rule) {
111 normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
112 } elseif ('[:nonspacing mark:] remove' === $rule) {
113 $s = preg_replace('/\p{Mn}++/u', '', $s);
114 } elseif ('latin-ascii' === $rule) {
115 $s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
116 } elseif ('de-ascii' === $rule) {
117 $s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
118 $s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
119 } elseif (\function_exists('transliterator_transliterate')) {
120 if (null === $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule)) {
121 if ('any-latin/bgn' === $rule) {
122 $rule = 'any-latin';
123 $transliterator = self::$transliterators[$rule] ??= \Transliterator::create($rule);
124 }
125
126 if (null === $transliterator) {
127 throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
128 }
129
130 self::$transliterators['any-latin/bgn'] = $transliterator;
131 }
132
133 $s = $transliterator->transliterate($s);
134 }
135 } elseif (!\function_exists('iconv')) {
136 $s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
137 } else {
138 $s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
139 $c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
140
141 if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
142 throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
143 }
144
145 return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
146 }, $s);
147 }
148 }
149
150 $str->string .= $s;
151
152 return $str;
153 }
154
155 public function camel(): static
156 {
157 $str = clone $this;
158 $str->string = str_replace(' ', '', preg_replace_callback('/\b.(?!\p{Lu})/u', static function ($m) {
159 static $i = 0;
160
161 return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
162 }, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));
163
164 return $str;
165 }
166
167 /**
168 * @return int[]
169 */
170 public function codePointsAt(int $offset): array
171 {
172 $str = $this->slice($offset, 1);
173
174 if ('' === $str->string) {
175 return [];
176 }
177
178 $codePoints = [];
179
180 foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
181 $codePoints[] = mb_ord($c, 'UTF-8');
182 }
183
184 return $codePoints;
185 }
186
187 public function folded(bool $compat = true): static
188 {
189 $str = clone $this;
190
191 if (!$compat || !\defined('Normalizer::NFKC_CF')) {
192 $str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
193 $str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $str->string), 'UTF-8');
194 } else {
195 $str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
196 }
197
198 return $str;
199 }
200
201 public function join(array $strings, ?string $lastGlue = null): static
202 {
203 $str = clone $this;
204
205 $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
206 $str->string = implode($this->string, $strings).$tail;
207
208 if (!preg_match('//u', $str->string)) {
209 throw new InvalidArgumentException('Invalid UTF-8 string.');
210 }
211
212 return $str;
213 }
214
215 public function lower(): static
216 {
217 $str = clone $this;
218 $str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8');
219
220 return $str;
221 }
222
223 /**
224 * @param string $locale In the format language_region (e.g. tr_TR)
225 */
226 public function localeLower(string $locale): static
227 {
228 if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Lower')) {
229 $str = clone $this;
230 $str->string = $transliterator->transliterate($str->string);
231
232 return $str;
233 }
234
235 return $this->lower();
236 }
237
238 public function match(string $regexp, int $flags = 0, int $offset = 0): array
239 {
240 $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
241
242 if ($this->ignoreCase) {
243 $regexp .= 'i';
244 }
245
246 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
247
248 try {
249 if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
250 throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
251 }
252 } finally {
253 restore_error_handler();
254 }
255
256 return $matches;
257 }
258
259 public function normalize(int $form = self::NFC): static
260 {
261 if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
262 throw new InvalidArgumentException('Unsupported normalization form.');
263 }
264
265 $str = clone $this;
266 normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
267
268 return $str;
269 }
270
271 public function padBoth(int $length, string $padStr = ' '): static
272 {
273 if ('' === $padStr || !preg_match('//u', $padStr)) {
274 throw new InvalidArgumentException('Invalid UTF-8 string.');
275 }
276
277 $pad = clone $this;
278 $pad->string = $padStr;
279
280 return $this->pad($length, $pad, \STR_PAD_BOTH);
281 }
282
283 public function padEnd(int $length, string $padStr = ' '): static
284 {
285 if ('' === $padStr || !preg_match('//u', $padStr)) {
286 throw new InvalidArgumentException('Invalid UTF-8 string.');
287 }
288
289 $pad = clone $this;
290 $pad->string = $padStr;
291
292 return $this->pad($length, $pad, \STR_PAD_RIGHT);
293 }
294
295 public function padStart(int $length, string $padStr = ' '): static
296 {
297 if ('' === $padStr || !preg_match('//u', $padStr)) {
298 throw new InvalidArgumentException('Invalid UTF-8 string.');
299 }
300
301 $pad = clone $this;
302 $pad->string = $padStr;
303
304 return $this->pad($length, $pad, \STR_PAD_LEFT);
305 }
306
307 public function replaceMatches(string $fromRegexp, string|callable $to): static
308 {
309 if ($this->ignoreCase) {
310 $fromRegexp .= 'i';
311 }
312
313 if (\is_array($to) || $to instanceof \Closure) {
314 $replace = 'preg_replace_callback';
315 $to = static function (array $m) use ($to): string {
316 $to = $to($m);
317
318 if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
319 throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
320 }
321
322 return $to;
323 };
324 } elseif ('' !== $to && !preg_match('//u', $to)) {
325 throw new InvalidArgumentException('Invalid UTF-8 string.');
326 } else {
327 $replace = 'preg_replace';
328 }
329
330 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
331
332 try {
333 if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
334 $lastError = preg_last_error();
335
336 foreach (get_defined_constants(true)['pcre'] as $k => $v) {
337 if ($lastError === $v && str_ends_with($k, '_ERROR')) {
338 throw new RuntimeException('Matching failed with '.$k.'.');
339 }
340 }
341
342 throw new RuntimeException('Matching failed with unknown error code.');
343 }
344 } finally {
345 restore_error_handler();
346 }
347
348 $str = clone $this;
349 $str->string = $string;
350
351 return $str;
352 }
353
354 public function reverse(): static
355 {
356 $str = clone $this;
357 $str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
358
359 return $str;
360 }
361
362 public function snake(): static
363 {
364 $str = $this->camel();
365 $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');
366
367 return $str;
368 }
369
370 public function title(bool $allWords = false): static
371 {
372 $str = clone $this;
373
374 $limit = $allWords ? -1 : 1;
375
376 $str->string = preg_replace_callback('/\b./u', static fn (array $m): string => mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8'), $str->string, $limit);
377
378 return $str;
379 }
380
381 /**
382 * @param string $locale In the format language_region (e.g. tr_TR)
383 */
384 public function localeTitle(string $locale): static
385 {
386 if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Title')) {
387 $str = clone $this;
388 $str->string = $transliterator->transliterate($str->string);
389
390 return $str;
391 }
392
393 return $this->title();
394 }
395
396 public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static
397 {
398 if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
399 throw new InvalidArgumentException('Invalid UTF-8 chars.');
400 }
401 $chars = preg_quote($chars);
402
403 $str = clone $this;
404 $str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string);
405
406 return $str;
407 }
408
409 public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static
410 {
411 if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
412 throw new InvalidArgumentException('Invalid UTF-8 chars.');
413 }
414 $chars = preg_quote($chars);
415
416 $str = clone $this;
417 $str->string = preg_replace("{[$chars]++$}uD", '', $str->string);
418
419 return $str;
420 }
421
422 public function trimPrefix($prefix): static
423 {
424 if (!$this->ignoreCase) {
425 return parent::trimPrefix($prefix);
426 }
427
428 $str = clone $this;
429
430 if ($prefix instanceof \Traversable) {
431 $prefix = iterator_to_array($prefix, false);
432 } elseif ($prefix instanceof parent) {
433 $prefix = $prefix->string;
434 }
435
436 $prefix = implode('|', array_map('preg_quote', (array) $prefix));
437 $str->string = preg_replace("{^(?:$prefix)}iuD", '', $this->string);
438
439 return $str;
440 }
441
442 public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): static
443 {
444 if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
445 throw new InvalidArgumentException('Invalid UTF-8 chars.');
446 }
447 $chars = preg_quote($chars);
448
449 $str = clone $this;
450 $str->string = preg_replace("{^[$chars]++}uD", '', $str->string);
451
452 return $str;
453 }
454
455 public function trimSuffix($suffix): static
456 {
457 if (!$this->ignoreCase) {
458 return parent::trimSuffix($suffix);
459 }
460
461 $str = clone $this;
462
463 if ($suffix instanceof \Traversable) {
464 $suffix = iterator_to_array($suffix, false);
465 } elseif ($suffix instanceof parent) {
466 $suffix = $suffix->string;
467 }
468
469 $suffix = implode('|', array_map('preg_quote', (array) $suffix));
470 $str->string = preg_replace("{(?:$suffix)$}iuD", '', $this->string);
471
472 return $str;
473 }
474
475 public function upper(): static
476 {
477 $str = clone $this;
478 $str->string = mb_strtoupper($str->string, 'UTF-8');
479
480 return $str;
481 }
482
483 /**
484 * @param string $locale In the format language_region (e.g. tr_TR)
485 */
486 public function localeUpper(string $locale): static
487 {
488 if (null !== $transliterator = $this->getLocaleTransliterator($locale, 'Upper')) {
489 $str = clone $this;
490 $str->string = $transliterator->transliterate($str->string);
491
492 return $str;
493 }
494
495 return $this->upper();
496 }
497
498 public function width(bool $ignoreAnsiDecoration = true): int
499 {
500 $width = 0;
501 $s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
502
503 if (str_contains($s, "\r")) {
504 $s = str_replace(["\r\n", "\r"], "\n", $s);
505 }
506
507 if (!$ignoreAnsiDecoration) {
508 $s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s);
509 }
510
511 foreach (explode("\n", $s) as $s) {
512 if ($ignoreAnsiDecoration) {
513 $s = preg_replace('/(?:\x1B(?:
514 \[ [\x30-\x3F]*+ [\x20-\x2F]*+ [\x40-\x7E]
515 | [P\]X^_] .*? \x1B\\\\
516 | [\x41-\x7E]
517 )|[\p{Cc}\x7F]++)/xu', '', $s);
518 }
519
520 $lineWidth = $this->wcswidth($s);
521
522 if ($lineWidth > $width) {
523 $width = $lineWidth;
524 }
525 }
526
527 return $width;
528 }
529
530 private function pad(int $len, self $pad, int $type): static
531 {
532 $sLen = $this->length();
533
534 if ($len <= $sLen) {
535 return clone $this;
536 }
537
538 $padLen = $pad->length();
539 $freeLen = $len - $sLen;
540 $len = $freeLen % $padLen;
541
542 switch ($type) {
543 case \STR_PAD_RIGHT:
544 return $this->append(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
545
546 case \STR_PAD_LEFT:
547 return $this->prepend(str_repeat($pad->string, intdiv($freeLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
548
549 case \STR_PAD_BOTH:
550 $freeLen /= 2;
551
552 $rightLen = ceil($freeLen);
553 $len = $rightLen % $padLen;
554 $str = $this->append(str_repeat($pad->string, intdiv($rightLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
555
556 $leftLen = floor($freeLen);
557 $len = $leftLen % $padLen;
558
559 return $str->prepend(str_repeat($pad->string, intdiv($leftLen, $padLen)).($len ? $pad->slice(0, $len) : ''));
560
561 default:
562 throw new InvalidArgumentException('Invalid padding type.');
563 }
564 }
565
566 /**
567 * Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
568 */
569 private function wcswidth(string $string): int
570 {
571 $width = 0;
572
573 foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
574 $codePoint = mb_ord($c, 'UTF-8');
575
576 if (0 === $codePoint // NULL
577 || 0x034F === $codePoint // COMBINING GRAPHEME JOINER
578 || (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK
579 || 0x2028 === $codePoint // LINE SEPARATOR
580 || 0x2029 === $codePoint // PARAGRAPH SEPARATOR
581 || (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE
582 || (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR
583 ) {
584 continue;
585 }
586
587 // Non printable characters
588 if (32 > $codePoint // C0 control characters
589 || (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL
590 ) {
591 return -1;
592 }
593
594 self::$tableZero ??= require __DIR__.'/Resources/data/wcswidth_table_zero.php';
595
596 if ($codePoint >= self::$tableZero[0][0] && $codePoint <= self::$tableZero[$ubound = \count(self::$tableZero) - 1][1]) {
597 $lbound = 0;
598 while ($ubound >= $lbound) {
599 $mid = floor(($lbound + $ubound) / 2);
600
601 if ($codePoint > self::$tableZero[$mid][1]) {
602 $lbound = $mid + 1;
603 } elseif ($codePoint < self::$tableZero[$mid][0]) {
604 $ubound = $mid - 1;
605 } else {
606 continue 2;
607 }
608 }
609 }
610
611 self::$tableWide ??= require __DIR__.'/Resources/data/wcswidth_table_wide.php';
612
613 if ($codePoint >= self::$tableWide[0][0] && $codePoint <= self::$tableWide[$ubound = \count(self::$tableWide) - 1][1]) {
614 $lbound = 0;
615 while ($ubound >= $lbound) {
616 $mid = floor(($lbound + $ubound) / 2);
617
618 if ($codePoint > self::$tableWide[$mid][1]) {
619 $lbound = $mid + 1;
620 } elseif ($codePoint < self::$tableWide[$mid][0]) {
621 $ubound = $mid - 1;
622 } else {
623 $width += 2;
624
625 continue 2;
626 }
627 }
628 }
629
630 ++$width;
631 }
632
633 return $width;
634 }
635
636 private function getLocaleTransliterator(string $locale, string $id): ?\Transliterator
637 {
638 $rule = $locale.'-'.$id;
639 if (\array_key_exists($rule, self::$transliterators)) {
640 return self::$transliterators[$rule];
641 }
642
643 if (null !== $transliterator = self::$transliterators[$rule] = \Transliterator::create($rule)) {
644 return $transliterator;
645 }
646
647 // Try to find a parent locale (nl_BE -> nl)
648 if (false === $i = strpos($locale, '_')) {
649 return null;
650 }
651
652 $parentRule = substr_replace($locale, '-'.$id, $i);
653
654 // Parent locale was already cached, return and store as current locale
655 if (\array_key_exists($parentRule, self::$transliterators)) {
656 return self::$transliterators[$rule] = self::$transliterators[$parentRule];
657 }
658
659 // Create transliterator based on parent locale and cache the result on both initial and parent locale values
660 $transliterator = \Transliterator::create($parentRule);
661
662 return self::$transliterators[$rule] = self::$transliterators[$parentRule] = $transliterator;
663 }
664}
diff --git a/vendor/symfony/string/ByteString.php b/vendor/symfony/string/ByteString.php
new file mode 100644
index 0000000..e6b56ae
--- /dev/null
+++ b/vendor/symfony/string/ByteString.php
@@ -0,0 +1,490 @@
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
12namespace Symfony\Component\String;
13
14use Random\Randomizer;
15use Symfony\Component\String\Exception\ExceptionInterface;
16use Symfony\Component\String\Exception\InvalidArgumentException;
17use Symfony\Component\String\Exception\RuntimeException;
18
19/**
20 * Represents a binary-safe string of bytes.
21 *
22 * @author Nicolas Grekas <p@tchwork.com>
23 * @author Hugo Hamon <hugohamon@neuf.fr>
24 *
25 * @throws ExceptionInterface
26 */
27class ByteString extends AbstractString
28{
29 private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
30
31 public function __construct(string $string = '')
32 {
33 $this->string = $string;
34 }
35
36 /*
37 * The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
38 *
39 * https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
40 *
41 * Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
42 *
43 * Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
44 */
45
46 public static function fromRandom(int $length = 16, ?string $alphabet = null): self
47 {
48 if ($length <= 0) {
49 throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
50 }
51
52 $alphabet ??= self::ALPHABET_ALPHANUMERIC;
53 $alphabetSize = \strlen($alphabet);
54 $bits = (int) ceil(log($alphabetSize, 2.0));
55 if ($bits <= 0 || $bits > 56) {
56 throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
57 }
58
59 if (\PHP_VERSION_ID >= 80300) {
60 return new static((new Randomizer())->getBytesFromString($alphabet, $length));
61 }
62
63 $ret = '';
64 while ($length > 0) {
65 $urandomLength = (int) ceil(2 * $length * $bits / 8.0);
66 $data = random_bytes($urandomLength);
67 $unpackedData = 0;
68 $unpackedBits = 0;
69 for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
70 // Unpack 8 bits
71 $unpackedData = ($unpackedData << 8) | \ord($data[$i]);
72 $unpackedBits += 8;
73
74 // While we have enough bits to select a character from the alphabet, keep
75 // consuming the random data
76 for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
77 $index = ($unpackedData & ((1 << $bits) - 1));
78 $unpackedData >>= $bits;
79 // Unfortunately, the alphabet size is not necessarily a power of two.
80 // Worst case, it is 2^k + 1, which means we need (k+1) bits and we
81 // have around a 50% chance of missing as k gets larger
82 if ($index < $alphabetSize) {
83 $ret .= $alphabet[$index];
84 --$length;
85 }
86 }
87 }
88 }
89
90 return new static($ret);
91 }
92
93 public function bytesAt(int $offset): array
94 {
95 $str = $this->string[$offset] ?? '';
96
97 return '' === $str ? [] : [\ord($str)];
98 }
99
100 public function append(string ...$suffix): static
101 {
102 $str = clone $this;
103 $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
104
105 return $str;
106 }
107
108 public function camel(): static
109 {
110 $str = clone $this;
111
112 $parts = explode(' ', trim(ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
113 $parts[0] = 1 !== \strlen($parts[0]) && ctype_upper($parts[0]) ? $parts[0] : lcfirst($parts[0]);
114 $str->string = implode('', $parts);
115
116 return $str;
117 }
118
119 public function chunk(int $length = 1): array
120 {
121 if (1 > $length) {
122 throw new InvalidArgumentException('The chunk length must be greater than zero.');
123 }
124
125 if ('' === $this->string) {
126 return [];
127 }
128
129 $str = clone $this;
130 $chunks = [];
131
132 foreach (str_split($this->string, $length) as $chunk) {
133 $str->string = $chunk;
134 $chunks[] = clone $str;
135 }
136
137 return $chunks;
138 }
139
140 public function endsWith(string|iterable|AbstractString $suffix): bool
141 {
142 if ($suffix instanceof AbstractString) {
143 $suffix = $suffix->string;
144 } elseif (!\is_string($suffix)) {
145 return parent::endsWith($suffix);
146 }
147
148 return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
149 }
150
151 public function equalsTo(string|iterable|AbstractString $string): bool
152 {
153 if ($string instanceof AbstractString) {
154 $string = $string->string;
155 } elseif (!\is_string($string)) {
156 return parent::equalsTo($string);
157 }
158
159 if ('' !== $string && $this->ignoreCase) {
160 return 0 === strcasecmp($string, $this->string);
161 }
162
163 return $string === $this->string;
164 }
165
166 public function folded(): static
167 {
168 $str = clone $this;
169 $str->string = strtolower($str->string);
170
171 return $str;
172 }
173
174 public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
175 {
176 if ($needle instanceof AbstractString) {
177 $needle = $needle->string;
178 } elseif (!\is_string($needle)) {
179 return parent::indexOf($needle, $offset);
180 }
181
182 if ('' === $needle) {
183 return null;
184 }
185
186 $i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);
187
188 return false === $i ? null : $i;
189 }
190
191 public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
192 {
193 if ($needle instanceof AbstractString) {
194 $needle = $needle->string;
195 } elseif (!\is_string($needle)) {
196 return parent::indexOfLast($needle, $offset);
197 }
198
199 if ('' === $needle) {
200 return null;
201 }
202
203 $i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);
204
205 return false === $i ? null : $i;
206 }
207
208 public function isUtf8(): bool
209 {
210 return '' === $this->string || preg_match('//u', $this->string);
211 }
212
213 public function join(array $strings, ?string $lastGlue = null): static
214 {
215 $str = clone $this;
216
217 $tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
218 $str->string = implode($this->string, $strings).$tail;
219
220 return $str;
221 }
222
223 public function length(): int
224 {
225 return \strlen($this->string);
226 }
227
228 public function lower(): static
229 {
230 $str = clone $this;
231 $str->string = strtolower($str->string);
232
233 return $str;
234 }
235
236 public function match(string $regexp, int $flags = 0, int $offset = 0): array
237 {
238 $match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
239
240 if ($this->ignoreCase) {
241 $regexp .= 'i';
242 }
243
244 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
245
246 try {
247 if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
248 throw new RuntimeException('Matching failed with error: '.preg_last_error_msg());
249 }
250 } finally {
251 restore_error_handler();
252 }
253
254 return $matches;
255 }
256
257 public function padBoth(int $length, string $padStr = ' '): static
258 {
259 $str = clone $this;
260 $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);
261
262 return $str;
263 }
264
265 public function padEnd(int $length, string $padStr = ' '): static
266 {
267 $str = clone $this;
268 $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);
269
270 return $str;
271 }
272
273 public function padStart(int $length, string $padStr = ' '): static
274 {
275 $str = clone $this;
276 $str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);
277
278 return $str;
279 }
280
281 public function prepend(string ...$prefix): static
282 {
283 $str = clone $this;
284 $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;
285
286 return $str;
287 }
288
289 public function replace(string $from, string $to): static
290 {
291 $str = clone $this;
292
293 if ('' !== $from) {
294 $str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
295 }
296
297 return $str;
298 }
299
300 public function replaceMatches(string $fromRegexp, string|callable $to): static
301 {
302 if ($this->ignoreCase) {
303 $fromRegexp .= 'i';
304 }
305
306 $replace = \is_array($to) || $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
307
308 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
309
310 try {
311 if (null === $string = $replace($fromRegexp, $to, $this->string)) {
312 $lastError = preg_last_error();
313
314 foreach (get_defined_constants(true)['pcre'] as $k => $v) {
315 if ($lastError === $v && str_ends_with($k, '_ERROR')) {
316 throw new RuntimeException('Matching failed with '.$k.'.');
317 }
318 }
319
320 throw new RuntimeException('Matching failed with unknown error code.');
321 }
322 } finally {
323 restore_error_handler();
324 }
325
326 $str = clone $this;
327 $str->string = $string;
328
329 return $str;
330 }
331
332 public function reverse(): static
333 {
334 $str = clone $this;
335 $str->string = strrev($str->string);
336
337 return $str;
338 }
339
340 public function slice(int $start = 0, ?int $length = null): static
341 {
342 $str = clone $this;
343 $str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
344
345 return $str;
346 }
347
348 public function snake(): static
349 {
350 $str = $this->camel();
351 $str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));
352
353 return $str;
354 }
355
356 public function splice(string $replacement, int $start = 0, ?int $length = null): static
357 {
358 $str = clone $this;
359 $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
360
361 return $str;
362 }
363
364 public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
365 {
366 if (1 > $limit ??= \PHP_INT_MAX) {
367 throw new InvalidArgumentException('Split limit must be a positive integer.');
368 }
369
370 if ('' === $delimiter) {
371 throw new InvalidArgumentException('Split delimiter is empty.');
372 }
373
374 if (null !== $flags) {
375 return parent::split($delimiter, $limit, $flags);
376 }
377
378 $str = clone $this;
379 $chunks = $this->ignoreCase
380 ? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
381 : explode($delimiter, $this->string, $limit);
382
383 foreach ($chunks as &$chunk) {
384 $str->string = $chunk;
385 $chunk = clone $str;
386 }
387
388 return $chunks;
389 }
390
391 public function startsWith(string|iterable|AbstractString $prefix): bool
392 {
393 if ($prefix instanceof AbstractString) {
394 $prefix = $prefix->string;
395 } elseif (!\is_string($prefix)) {
396 return parent::startsWith($prefix);
397 }
398
399 return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
400 }
401
402 public function title(bool $allWords = false): static
403 {
404 $str = clone $this;
405 $str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);
406
407 return $str;
408 }
409
410 public function toUnicodeString(?string $fromEncoding = null): UnicodeString
411 {
412 return new UnicodeString($this->toCodePointString($fromEncoding)->string);
413 }
414
415 public function toCodePointString(?string $fromEncoding = null): CodePointString
416 {
417 $u = new CodePointString();
418
419 if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
420 $u->string = $this->string;
421
422 return $u;
423 }
424
425 set_error_handler(static fn ($t, $m) => throw new InvalidArgumentException($m));
426
427 try {
428 try {
429 $validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
430 } catch (InvalidArgumentException $e) {
431 if (!\function_exists('iconv')) {
432 throw $e;
433 }
434
435 $u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);
436
437 return $u;
438 }
439 } finally {
440 restore_error_handler();
441 }
442
443 if (!$validEncoding) {
444 throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
445 }
446
447 $u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');
448
449 return $u;
450 }
451
452 public function trim(string $chars = " \t\n\r\0\x0B\x0C"): static
453 {
454 $str = clone $this;
455 $str->string = trim($str->string, $chars);
456
457 return $str;
458 }
459
460 public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): static
461 {
462 $str = clone $this;
463 $str->string = rtrim($str->string, $chars);
464
465 return $str;
466 }
467
468 public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): static
469 {
470 $str = clone $this;
471 $str->string = ltrim($str->string, $chars);
472
473 return $str;
474 }
475
476 public function upper(): static
477 {
478 $str = clone $this;
479 $str->string = strtoupper($str->string);
480
481 return $str;
482 }
483
484 public function width(bool $ignoreAnsiDecoration = true): int
485 {
486 $string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);
487
488 return (new CodePointString($string))->width($ignoreAnsiDecoration);
489 }
490}
diff --git a/vendor/symfony/string/CHANGELOG.md b/vendor/symfony/string/CHANGELOG.md
new file mode 100644
index 0000000..621cedf
--- /dev/null
+++ b/vendor/symfony/string/CHANGELOG.md
@@ -0,0 +1,45 @@
1CHANGELOG
2=========
3
47.1
5---
6
7 * Add `localeLower()`, `localeUpper()`, `localeTitle()` methods to `AbstractUnicodeString`
8
96.2
10---
11
12 * Add support for emoji in `AsciiSlugger`
13
145.4
15---
16
17 * Add `trimSuffix()` and `trimPrefix()` methods
18
195.3
20---
21
22 * Made `AsciiSlugger` fallback to parent locale's symbolsMap
23
245.2.0
25-----
26
27 * added a `FrenchInflector` class
28
295.1.0
30-----
31
32 * added the `AbstractString::reverse()` method
33 * made `AbstractString::width()` follow POSIX.1-2001
34 * added `LazyString` which provides memoizing stringable objects
35 * The component is not marked as `@experimental` anymore
36 * added the `s()` helper method to get either an `UnicodeString` or `ByteString` instance,
37 depending of the input string UTF-8 compliancy
38 * added `$cut` parameter to `Symfony\Component\String\AbstractString::truncate()`
39 * added `AbstractString::containsAny()`
40 * allow passing a string of custom characters to `ByteString::fromRandom()`
41
425.0.0
43-----
44
45 * added the component as experimental
diff --git a/vendor/symfony/string/CodePointString.php b/vendor/symfony/string/CodePointString.php
new file mode 100644
index 0000000..337bfc1
--- /dev/null
+++ b/vendor/symfony/string/CodePointString.php
@@ -0,0 +1,260 @@
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
12namespace Symfony\Component\String;
13
14use Symfony\Component\String\Exception\ExceptionInterface;
15use Symfony\Component\String\Exception\InvalidArgumentException;
16
17/**
18 * Represents a string of Unicode code points encoded as UTF-8.
19 *
20 * @author Nicolas Grekas <p@tchwork.com>
21 * @author Hugo Hamon <hugohamon@neuf.fr>
22 *
23 * @throws ExceptionInterface
24 */
25class CodePointString extends AbstractUnicodeString
26{
27 public function __construct(string $string = '')
28 {
29 if ('' !== $string && !preg_match('//u', $string)) {
30 throw new InvalidArgumentException('Invalid UTF-8 string.');
31 }
32
33 $this->string = $string;
34 }
35
36 public function append(string ...$suffix): static
37 {
38 $str = clone $this;
39 $str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
40
41 if (!preg_match('//u', $str->string)) {
42 throw new InvalidArgumentException('Invalid UTF-8 string.');
43 }
44
45 return $str;
46 }
47
48 public function chunk(int $length = 1): array
49 {
50 if (1 > $length) {
51 throw new InvalidArgumentException('The chunk length must be greater than zero.');
52 }
53
54 if ('' === $this->string) {
55 return [];
56 }
57
58 $rx = '/(';
59 while (65535 < $length) {
60 $rx .= '.{65535}';
61 $length -= 65535;
62 }
63 $rx .= '.{'.$length.'})/us';
64
65 $str = clone $this;
66 $chunks = [];
67
68 foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
69 $str->string = $chunk;
70 $chunks[] = clone $str;
71 }
72
73 return $chunks;
74 }
75
76 public function codePointsAt(int $offset): array
77 {
78 $str = $offset ? $this->slice($offset, 1) : $this;
79
80 return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')];
81 }
82
83 public function endsWith(string|iterable|AbstractString $suffix): bool
84 {
85 if ($suffix instanceof AbstractString) {
86 $suffix = $suffix->string;
87 } elseif (!\is_string($suffix)) {
88 return parent::endsWith($suffix);
89 }
90
91 if ('' === $suffix || !preg_match('//u', $suffix)) {
92 return false;
93 }
94
95 if ($this->ignoreCase) {
96 return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string);
97 }
98
99 return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix));
100 }
101
102 public function equalsTo(string|iterable|AbstractString $string): bool
103 {
104 if ($string instanceof AbstractString) {
105 $string = $string->string;
106 } elseif (!\is_string($string)) {
107 return parent::equalsTo($string);
108 }
109
110 if ('' !== $string && $this->ignoreCase) {
111 return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
112 }
113
114 return $string === $this->string;
115 }
116
117 public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
118 {
119 if ($needle instanceof AbstractString) {
120 $needle = $needle->string;
121 } elseif (!\is_string($needle)) {
122 return parent::indexOf($needle, $offset);
123 }
124
125 if ('' === $needle) {
126 return null;
127 }
128
129 $i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8');
130
131 return false === $i ? null : $i;
132 }
133
134 public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
135 {
136 if ($needle instanceof AbstractString) {
137 $needle = $needle->string;
138 } elseif (!\is_string($needle)) {
139 return parent::indexOfLast($needle, $offset);
140 }
141
142 if ('' === $needle) {
143 return null;
144 }
145
146 $i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8');
147
148 return false === $i ? null : $i;
149 }
150
151 public function length(): int
152 {
153 return mb_strlen($this->string, 'UTF-8');
154 }
155
156 public function prepend(string ...$prefix): static
157 {
158 $str = clone $this;
159 $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
160
161 if (!preg_match('//u', $str->string)) {
162 throw new InvalidArgumentException('Invalid UTF-8 string.');
163 }
164
165 return $str;
166 }
167
168 public function replace(string $from, string $to): static
169 {
170 $str = clone $this;
171
172 if ('' === $from || !preg_match('//u', $from)) {
173 return $str;
174 }
175
176 if ('' !== $to && !preg_match('//u', $to)) {
177 throw new InvalidArgumentException('Invalid UTF-8 string.');
178 }
179
180 if ($this->ignoreCase) {
181 $str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string));
182 } else {
183 $str->string = str_replace($from, $to, $this->string);
184 }
185
186 return $str;
187 }
188
189 public function slice(int $start = 0, ?int $length = null): static
190 {
191 $str = clone $this;
192 $str->string = mb_substr($this->string, $start, $length, 'UTF-8');
193
194 return $str;
195 }
196
197 public function splice(string $replacement, int $start = 0, ?int $length = null): static
198 {
199 if (!preg_match('//u', $replacement)) {
200 throw new InvalidArgumentException('Invalid UTF-8 string.');
201 }
202
203 $str = clone $this;
204 $start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0;
205 $length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length;
206 $str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
207
208 return $str;
209 }
210
211 public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
212 {
213 if (1 > $limit ??= \PHP_INT_MAX) {
214 throw new InvalidArgumentException('Split limit must be a positive integer.');
215 }
216
217 if ('' === $delimiter) {
218 throw new InvalidArgumentException('Split delimiter is empty.');
219 }
220
221 if (null !== $flags) {
222 return parent::split($delimiter.'u', $limit, $flags);
223 }
224
225 if (!preg_match('//u', $delimiter)) {
226 throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
227 }
228
229 $str = clone $this;
230 $chunks = $this->ignoreCase
231 ? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit)
232 : explode($delimiter, $this->string, $limit);
233
234 foreach ($chunks as &$chunk) {
235 $str->string = $chunk;
236 $chunk = clone $str;
237 }
238
239 return $chunks;
240 }
241
242 public function startsWith(string|iterable|AbstractString $prefix): bool
243 {
244 if ($prefix instanceof AbstractString) {
245 $prefix = $prefix->string;
246 } elseif (!\is_string($prefix)) {
247 return parent::startsWith($prefix);
248 }
249
250 if ('' === $prefix || !preg_match('//u', $prefix)) {
251 return false;
252 }
253
254 if ($this->ignoreCase) {
255 return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8');
256 }
257
258 return 0 === strncmp($this->string, $prefix, \strlen($prefix));
259 }
260}
diff --git a/vendor/symfony/string/Exception/ExceptionInterface.php b/vendor/symfony/string/Exception/ExceptionInterface.php
new file mode 100644
index 0000000..3619786
--- /dev/null
+++ b/vendor/symfony/string/Exception/ExceptionInterface.php
@@ -0,0 +1,16 @@
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
12namespace Symfony\Component\String\Exception;
13
14interface ExceptionInterface extends \Throwable
15{
16}
diff --git a/vendor/symfony/string/Exception/InvalidArgumentException.php b/vendor/symfony/string/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..6aa586b
--- /dev/null
+++ b/vendor/symfony/string/Exception/InvalidArgumentException.php
@@ -0,0 +1,16 @@
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
12namespace Symfony\Component\String\Exception;
13
14class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
15{
16}
diff --git a/vendor/symfony/string/Exception/RuntimeException.php b/vendor/symfony/string/Exception/RuntimeException.php
new file mode 100644
index 0000000..77cb091
--- /dev/null
+++ b/vendor/symfony/string/Exception/RuntimeException.php
@@ -0,0 +1,16 @@
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
12namespace Symfony\Component\String\Exception;
13
14class RuntimeException extends \RuntimeException implements ExceptionInterface
15{
16}
diff --git a/vendor/symfony/string/Inflector/EnglishInflector.php b/vendor/symfony/string/Inflector/EnglishInflector.php
new file mode 100644
index 0000000..77ebc13
--- /dev/null
+++ b/vendor/symfony/string/Inflector/EnglishInflector.php
@@ -0,0 +1,577 @@
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
12namespace Symfony\Component\String\Inflector;
13
14final class EnglishInflector implements InflectorInterface
15{
16 /**
17 * Map English plural to singular suffixes.
18 *
19 * @see http://english-zone.com/spelling/plurals.html
20 */
21 private const PLURAL_MAP = [
22 // First entry: plural suffix, reversed
23 // Second entry: length of plural suffix
24 // Third entry: Whether the suffix may succeed a vowel
25 // Fourth entry: Whether the suffix may succeed a consonant
26 // Fifth entry: singular suffix, normal
27
28 // bacteria (bacterium)
29 ['airetcab', 8, true, true, 'bacterium'],
30
31 // corpora (corpus)
32 ['aroproc', 7, true, true, 'corpus'],
33
34 // criteria (criterion)
35 ['airetirc', 8, true, true, 'criterion'],
36
37 // curricula (curriculum)
38 ['alucirruc', 9, true, true, 'curriculum'],
39
40 // genera (genus)
41 ['areneg', 6, true, true, 'genus'],
42
43 // media (medium)
44 ['aidem', 5, true, true, 'medium'],
45
46 // memoranda (memorandum)
47 ['adnaromem', 9, true, true, 'memorandum'],
48
49 // phenomena (phenomenon)
50 ['anemonehp', 9, true, true, 'phenomenon'],
51
52 // strata (stratum)
53 ['atarts', 6, true, true, 'stratum'],
54
55 // nebulae (nebula)
56 ['ea', 2, true, true, 'a'],
57
58 // services (service)
59 ['secivres', 8, true, true, 'service'],
60
61 // mice (mouse), lice (louse)
62 ['eci', 3, false, true, 'ouse'],
63
64 // geese (goose)
65 ['esee', 4, false, true, 'oose'],
66
67 // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
68 ['i', 1, true, true, 'us'],
69
70 // men (man), women (woman)
71 ['nem', 3, true, true, 'man'],
72
73 // children (child)
74 ['nerdlihc', 8, true, true, 'child'],
75
76 // oxen (ox)
77 ['nexo', 4, false, false, 'ox'],
78
79 // indices (index), appendices (appendix), prices (price)
80 ['seci', 4, false, true, ['ex', 'ix', 'ice']],
81
82 // codes (code)
83 ['sedoc', 5, false, true, 'code'],
84
85 // selfies (selfie)
86 ['seifles', 7, true, true, 'selfie'],
87
88 // zombies (zombie)
89 ['seibmoz', 7, true, true, 'zombie'],
90
91 // movies (movie)
92 ['seivom', 6, true, true, 'movie'],
93
94 // names (name)
95 ['seman', 5, true, false, 'name'],
96
97 // conspectuses (conspectus), prospectuses (prospectus)
98 ['sesutcep', 8, true, true, 'pectus'],
99
100 // feet (foot)
101 ['teef', 4, true, true, 'foot'],
102
103 // geese (goose)
104 ['eseeg', 5, true, true, 'goose'],
105
106 // teeth (tooth)
107 ['hteet', 5, true, true, 'tooth'],
108
109 // news (news)
110 ['swen', 4, true, true, 'news'],
111
112 // series (series)
113 ['seires', 6, true, true, 'series'],
114
115 // babies (baby)
116 ['sei', 3, false, true, 'y'],
117
118 // accesses (access), addresses (address), kisses (kiss)
119 ['sess', 4, true, false, 'ss'],
120
121 // statuses (status)
122 ['sesutats', 8, true, true, 'status'],
123
124 // analyses (analysis), ellipses (ellipsis), fungi (fungus),
125 // neuroses (neurosis), theses (thesis), emphases (emphasis),
126 // oases (oasis), crises (crisis), houses (house), bases (base),
127 // atlases (atlas)
128 ['ses', 3, true, true, ['s', 'se', 'sis']],
129
130 // objectives (objective), alternative (alternatives)
131 ['sevit', 5, true, true, 'tive'],
132
133 // drives (drive)
134 ['sevird', 6, false, true, 'drive'],
135
136 // lives (life), wives (wife)
137 ['sevi', 4, false, true, 'ife'],
138
139 // moves (move)
140 ['sevom', 5, true, true, 'move'],
141
142 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
143 ['sev', 3, true, true, ['f', 've', 'ff']],
144
145 // axes (axis), axes (ax), axes (axe)
146 ['sexa', 4, false, false, ['ax', 'axe', 'axis']],
147
148 // indexes (index), matrixes (matrix)
149 ['sex', 3, true, false, 'x'],
150
151 // quizzes (quiz)
152 ['sezz', 4, true, false, 'z'],
153
154 // bureaus (bureau)
155 ['suae', 4, false, true, 'eau'],
156
157 // fees (fee), trees (tree), employees (employee)
158 ['see', 3, true, true, 'ee'],
159
160 // edges (edge)
161 ['segd', 4, true, true, 'dge'],
162
163 // roses (rose), garages (garage), cassettes (cassette),
164 // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
165 // shoes (shoe)
166 ['se', 2, true, true, ['', 'e']],
167
168 // status (status)
169 ['sutats', 6, true, true, 'status'],
170
171 // tags (tag)
172 ['s', 1, true, true, ''],
173
174 // chateaux (chateau)
175 ['xuae', 4, false, true, 'eau'],
176
177 // people (person)
178 ['elpoep', 6, true, true, 'person'],
179 ];
180
181 /**
182 * Map English singular to plural suffixes.
183 *
184 * @see http://english-zone.com/spelling/plurals.html
185 */
186 private const SINGULAR_MAP = [
187 // First entry: singular suffix, reversed
188 // Second entry: length of singular suffix
189 // Third entry: Whether the suffix may succeed a vowel
190 // Fourth entry: Whether the suffix may succeed a consonant
191 // Fifth entry: plural suffix, normal
192
193 // axes (axis)
194 ['sixa', 4, false, false, 'axes'],
195
196 // criterion (criteria)
197 ['airetirc', 8, false, false, 'criterion'],
198
199 // nebulae (nebula)
200 ['aluben', 6, false, false, 'nebulae'],
201
202 // children (child)
203 ['dlihc', 5, true, true, 'children'],
204
205 // prices (price)
206 ['eci', 3, false, true, 'ices'],
207
208 // services (service)
209 ['ecivres', 7, true, true, 'services'],
210
211 // lives (life), wives (wife)
212 ['efi', 3, false, true, 'ives'],
213
214 // selfies (selfie)
215 ['eifles', 6, true, true, 'selfies'],
216
217 // movies (movie)
218 ['eivom', 5, true, true, 'movies'],
219
220 // lice (louse)
221 ['esuol', 5, false, true, 'lice'],
222
223 // mice (mouse)
224 ['esuom', 5, false, true, 'mice'],
225
226 // geese (goose)
227 ['esoo', 4, false, true, 'eese'],
228
229 // houses (house), bases (base)
230 ['es', 2, true, true, 'ses'],
231
232 // geese (goose)
233 ['esoog', 5, true, true, 'geese'],
234
235 // caves (cave)
236 ['ev', 2, true, true, 'ves'],
237
238 // drives (drive)
239 ['evird', 5, false, true, 'drives'],
240
241 // objectives (objective), alternative (alternatives)
242 ['evit', 4, true, true, 'tives'],
243
244 // moves (move)
245 ['evom', 4, true, true, 'moves'],
246
247 // staves (staff)
248 ['ffats', 5, true, true, 'staves'],
249
250 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
251 ['ff', 2, true, true, 'ffs'],
252
253 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
254 ['f', 1, true, true, ['fs', 'ves']],
255
256 // arches (arch)
257 ['hc', 2, true, true, 'ches'],
258
259 // bushes (bush)
260 ['hs', 2, true, true, 'shes'],
261
262 // teeth (tooth)
263 ['htoot', 5, true, true, 'teeth'],
264
265 // albums (album)
266 ['mubla', 5, true, true, 'albums'],
267
268 // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum)
269 ['mu', 2, true, true, 'a'],
270
271 // men (man), women (woman)
272 ['nam', 3, true, true, 'men'],
273
274 // people (person)
275 ['nosrep', 6, true, true, ['persons', 'people']],
276
277 // criteria (criterion)
278 ['noiretirc', 9, true, true, 'criteria'],
279
280 // phenomena (phenomenon)
281 ['nonemonehp', 10, true, true, 'phenomena'],
282
283 // echoes (echo)
284 ['ohce', 4, true, true, 'echoes'],
285
286 // heroes (hero)
287 ['oreh', 4, true, true, 'heroes'],
288
289 // atlases (atlas)
290 ['salta', 5, true, true, 'atlases'],
291
292 // aliases (alias)
293 ['saila', 5, true, true, 'aliases'],
294
295 // irises (iris)
296 ['siri', 4, true, true, 'irises'],
297
298 // analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
299 // theses (thesis), emphases (emphasis), oases (oasis),
300 // crises (crisis)
301 ['sis', 3, true, true, 'ses'],
302
303 // accesses (access), addresses (address), kisses (kiss)
304 ['ss', 2, true, false, 'sses'],
305
306 // syllabi (syllabus)
307 ['suballys', 8, true, true, 'syllabi'],
308
309 // buses (bus)
310 ['sub', 3, true, true, 'buses'],
311
312 // circuses (circus)
313 ['suc', 3, true, true, 'cuses'],
314
315 // hippocampi (hippocampus)
316 ['supmacoppih', 11, false, false, 'hippocampi'],
317
318 // campuses (campus)
319 ['sup', 3, true, true, 'puses'],
320
321 // status (status)
322 ['sutats', 6, true, true, ['status', 'statuses']],
323
324 // conspectuses (conspectus), prospectuses (prospectus)
325 ['sutcep', 6, true, true, 'pectuses'],
326
327 // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
328 ['su', 2, true, true, 'i'],
329
330 // news (news)
331 ['swen', 4, true, true, 'news'],
332
333 // feet (foot)
334 ['toof', 4, true, true, 'feet'],
335
336 // chateaux (chateau), bureaus (bureau)
337 ['uae', 3, false, true, ['eaus', 'eaux']],
338
339 // oxen (ox)
340 ['xo', 2, false, false, 'oxen'],
341
342 // hoaxes (hoax)
343 ['xaoh', 4, true, false, 'hoaxes'],
344
345 // indices (index)
346 ['xedni', 5, false, true, ['indicies', 'indexes']],
347
348 // boxes (box)
349 ['xo', 2, false, true, 'oxes'],
350
351 // indexes (index), matrixes (matrix)
352 ['x', 1, true, false, ['cies', 'xes']],
353
354 // appendices (appendix)
355 ['xi', 2, false, true, 'ices'],
356
357 // babies (baby)
358 ['y', 1, false, true, 'ies'],
359
360 // quizzes (quiz)
361 ['ziuq', 4, true, false, 'quizzes'],
362
363 // waltzes (waltz)
364 ['z', 1, true, true, 'zes'],
365 ];
366
367 /**
368 * A list of words which should not be inflected, reversed.
369 */
370 private const UNINFLECTED = [
371 '',
372
373 // data
374 'atad',
375
376 // deer
377 'reed',
378
379 // equipment
380 'tnempiuqe',
381
382 // feedback
383 'kcabdeef',
384
385 // fish
386 'hsif',
387
388 // health
389 'htlaeh',
390
391 // history
392 'yrotsih',
393
394 // info
395 'ofni',
396
397 // information
398 'noitamrofni',
399
400 // money
401 'yenom',
402
403 // moose
404 'esoom',
405
406 // series
407 'seires',
408
409 // sheep
410 'peehs',
411
412 // species
413 'seiceps',
414
415 // traffic
416 'ciffart',
417
418 // aircraft
419 'tfarcria',
420
421 // hardware
422 'erawdrah',
423 ];
424
425 public function singularize(string $plural): array
426 {
427 $pluralRev = strrev($plural);
428 $lowerPluralRev = strtolower($pluralRev);
429 $pluralLength = \strlen($lowerPluralRev);
430
431 // Check if the word is one which is not inflected, return early if so
432 if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) {
433 return [$plural];
434 }
435
436 // The outer loop iterates over the entries of the plural table
437 // The inner loop $j iterates over the characters of the plural suffix
438 // in the plural table to compare them with the characters of the actual
439 // given plural suffix
440 foreach (self::PLURAL_MAP as $map) {
441 $suffix = $map[0];
442 $suffixLength = $map[1];
443 $j = 0;
444
445 // Compare characters in the plural table and of the suffix of the
446 // given plural one by one
447 while ($suffix[$j] === $lowerPluralRev[$j]) {
448 // Let $j point to the next character
449 ++$j;
450
451 // Successfully compared the last character
452 // Add an entry with the singular suffix to the singular array
453 if ($j === $suffixLength) {
454 // Is there any character preceding the suffix in the plural string?
455 if ($j < $pluralLength) {
456 $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]);
457
458 if (!$map[2] && $nextIsVowel) {
459 // suffix may not succeed a vowel but next char is one
460 break;
461 }
462
463 if (!$map[3] && !$nextIsVowel) {
464 // suffix may not succeed a consonant but next char is one
465 break;
466 }
467 }
468
469 $newBase = substr($plural, 0, $pluralLength - $suffixLength);
470 $newSuffix = $map[4];
471
472 // Check whether the first character in the plural suffix
473 // is uppercased. If yes, uppercase the first character in
474 // the singular suffix too
475 $firstUpper = ctype_upper($pluralRev[$j - 1]);
476
477 if (\is_array($newSuffix)) {
478 $singulars = [];
479
480 foreach ($newSuffix as $newSuffixEntry) {
481 $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
482 }
483
484 return $singulars;
485 }
486
487 return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
488 }
489
490 // Suffix is longer than word
491 if ($j === $pluralLength) {
492 break;
493 }
494 }
495 }
496
497 // Assume that plural and singular is identical
498 return [$plural];
499 }
500
501 public function pluralize(string $singular): array
502 {
503 $singularRev = strrev($singular);
504 $lowerSingularRev = strtolower($singularRev);
505 $singularLength = \strlen($lowerSingularRev);
506
507 // Check if the word is one which is not inflected, return early if so
508 if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) {
509 return [$singular];
510 }
511
512 // The outer loop iterates over the entries of the singular table
513 // The inner loop $j iterates over the characters of the singular suffix
514 // in the singular table to compare them with the characters of the actual
515 // given singular suffix
516 foreach (self::SINGULAR_MAP as $map) {
517 $suffix = $map[0];
518 $suffixLength = $map[1];
519 $j = 0;
520
521 // Compare characters in the singular table and of the suffix of the
522 // given plural one by one
523
524 while ($suffix[$j] === $lowerSingularRev[$j]) {
525 // Let $j point to the next character
526 ++$j;
527
528 // Successfully compared the last character
529 // Add an entry with the plural suffix to the plural array
530 if ($j === $suffixLength) {
531 // Is there any character preceding the suffix in the plural string?
532 if ($j < $singularLength) {
533 $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]);
534
535 if (!$map[2] && $nextIsVowel) {
536 // suffix may not succeed a vowel but next char is one
537 break;
538 }
539
540 if (!$map[3] && !$nextIsVowel) {
541 // suffix may not succeed a consonant but next char is one
542 break;
543 }
544 }
545
546 $newBase = substr($singular, 0, $singularLength - $suffixLength);
547 $newSuffix = $map[4];
548
549 // Check whether the first character in the singular suffix
550 // is uppercased. If yes, uppercase the first character in
551 // the singular suffix too
552 $firstUpper = ctype_upper($singularRev[$j - 1]);
553
554 if (\is_array($newSuffix)) {
555 $plurals = [];
556
557 foreach ($newSuffix as $newSuffixEntry) {
558 $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
559 }
560
561 return $plurals;
562 }
563
564 return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
565 }
566
567 // Suffix is longer than word
568 if ($j === $singularLength) {
569 break;
570 }
571 }
572 }
573
574 // Assume that plural is singular with a trailing `s`
575 return [$singular.'s'];
576 }
577}
diff --git a/vendor/symfony/string/Inflector/FrenchInflector.php b/vendor/symfony/string/Inflector/FrenchInflector.php
new file mode 100644
index 0000000..955abbf
--- /dev/null
+++ b/vendor/symfony/string/Inflector/FrenchInflector.php
@@ -0,0 +1,151 @@
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
12namespace Symfony\Component\String\Inflector;
13
14/**
15 * French inflector.
16 *
17 * This class does only inflect nouns; not adjectives nor composed words like "soixante-dix".
18 */
19final class FrenchInflector implements InflectorInterface
20{
21 /**
22 * A list of all rules for pluralise.
23 *
24 * @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php
25 */
26 private const PLURALIZE_REGEXP = [
27 // First entry: regexp
28 // Second entry: replacement
29
30 // Words finishing with "s", "x" or "z" are invariables
31 // Les mots finissant par "s", "x" ou "z" sont invariables
32 ['/(s|x|z)$/i', '\1'],
33
34 // Words finishing with "eau" are pluralized with a "x"
35 // Les mots finissant par "eau" prennent tous un "x" au pluriel
36 ['/(eau)$/i', '\1x'],
37
38 // Words finishing with "au" are pluralized with a "x" excepted "landau"
39 // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
40 ['/^(landau)$/i', '\1s'],
41 ['/(au)$/i', '\1x'],
42
43 // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
44 // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
45 ['/^(pneu|bleu|émeu)$/i', '\1s'],
46 ['/(eu)$/i', '\1x'],
47
48 // Words finishing with "al" are pluralized with a "aux" excepted
49 // Les mots finissant en "al" se terminent en "aux" sauf
50 ['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'],
51 ['/al$/i', '\1aux'],
52
53 // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
54 ['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'],
55
56 // Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel
57 ['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'],
58
59 // Invariable words
60 ['/^(cinquante|soixante|mille)$/i', '\1'],
61
62 // French titles
63 ['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'],
64 ['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'],
65 ];
66
67 /**
68 * A list of all rules for singularize.
69 */
70 private const SINGULARIZE_REGEXP = [
71 // First entry: regexp
72 // Second entry: replacement
73
74 // Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
75 ['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'],
76
77 // Words finishing with "eau" are pluralized with a "x"
78 // Les mots finissant par "eau" prennent tous un "x" au pluriel
79 ['/(eau)x$/i', '\1'],
80
81 // Words finishing with "al" are pluralized with a "aux" expected
82 // Les mots finissant en "al" se terminent en "aux" sauf
83 ['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'],
84
85 // Words finishing with "au" are pluralized with a "x" excepted "landau"
86 // Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
87 ['/(au)x$/i', '\1'],
88
89 // Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
90 // Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
91 ['/(eu)x$/i', '\1'],
92
93 // Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou
94 // Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou
95 ['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'],
96
97 // French titles
98 ['/^mes(dame|demoiselle)s$/', 'ma\1'],
99 ['/^Mes(dame|demoiselle)s$/', 'Ma\1'],
100 ['/^mes(sieur|seigneur)s$/', 'mon\1'],
101 ['/^Mes(sieur|seigneur)s$/', 'Mon\1'],
102
103 // Default rule
104 ['/s$/i', ''],
105 ];
106
107 /**
108 * A list of words which should not be inflected.
109 * This list is only used by singularize.
110 */
111 private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sans|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i';
112
113 public function singularize(string $plural): array
114 {
115 if ($this->isInflectedWord($plural)) {
116 return [$plural];
117 }
118
119 foreach (self::SINGULARIZE_REGEXP as $rule) {
120 [$regexp, $replace] = $rule;
121
122 if (1 === preg_match($regexp, $plural)) {
123 return [preg_replace($regexp, $replace, $plural)];
124 }
125 }
126
127 return [$plural];
128 }
129
130 public function pluralize(string $singular): array
131 {
132 if ($this->isInflectedWord($singular)) {
133 return [$singular];
134 }
135
136 foreach (self::PLURALIZE_REGEXP as $rule) {
137 [$regexp, $replace] = $rule;
138
139 if (1 === preg_match($regexp, $singular)) {
140 return [preg_replace($regexp, $replace, $singular)];
141 }
142 }
143
144 return [$singular.'s'];
145 }
146
147 private function isInflectedWord(string $word): bool
148 {
149 return 1 === preg_match(self::UNINFLECTED, $word);
150 }
151}
diff --git a/vendor/symfony/string/Inflector/InflectorInterface.php b/vendor/symfony/string/Inflector/InflectorInterface.php
new file mode 100644
index 0000000..67f2834
--- /dev/null
+++ b/vendor/symfony/string/Inflector/InflectorInterface.php
@@ -0,0 +1,33 @@
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
12namespace Symfony\Component\String\Inflector;
13
14interface InflectorInterface
15{
16 /**
17 * Returns the singular forms of a string.
18 *
19 * If the method can't determine the form with certainty, several possible singulars are returned.
20 *
21 * @return string[]
22 */
23 public function singularize(string $plural): array;
24
25 /**
26 * Returns the plural forms of a string.
27 *
28 * If the method can't determine the form with certainty, several possible plurals are returned.
29 *
30 * @return string[]
31 */
32 public function pluralize(string $singular): array;
33}
diff --git a/vendor/symfony/string/LICENSE b/vendor/symfony/string/LICENSE
new file mode 100644
index 0000000..f37c76b
--- /dev/null
+++ b/vendor/symfony/string/LICENSE
@@ -0,0 +1,19 @@
1Copyright (c) 2019-present Fabien Potencier
2
3Permission is hereby granted, free of charge, to any person obtaining a copy
4of this software and associated documentation files (the "Software"), to deal
5in the Software without restriction, including without limitation the rights
6to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7copies of the Software, and to permit persons to whom the Software is furnished
8to do so, subject to the following conditions:
9
10The above copyright notice and this permission notice shall be included in all
11copies or substantial portions of the Software.
12
13THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19THE SOFTWARE.
diff --git a/vendor/symfony/string/LazyString.php b/vendor/symfony/string/LazyString.php
new file mode 100644
index 0000000..8f2bbbf
--- /dev/null
+++ b/vendor/symfony/string/LazyString.php
@@ -0,0 +1,145 @@
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
12namespace Symfony\Component\String;
13
14/**
15 * A string whose value is computed lazily by a callback.
16 *
17 * @author Nicolas Grekas <p@tchwork.com>
18 */
19class LazyString implements \Stringable, \JsonSerializable
20{
21 private \Closure|string $value;
22
23 /**
24 * @param callable|array $callback A callable or a [Closure, method] lazy-callable
25 */
26 public static function fromCallable(callable|array $callback, mixed ...$arguments): static
27 {
28 if (\is_array($callback) && !\is_callable($callback) && !(($callback[0] ?? null) instanceof \Closure || 2 < \count($callback))) {
29 throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, '['.implode(', ', array_map('get_debug_type', $callback)).']'));
30 }
31
32 $lazyString = new static();
33 $lazyString->value = static function () use (&$callback, &$arguments): string {
34 static $value;
35
36 if (null !== $arguments) {
37 if (!\is_callable($callback)) {
38 $callback[0] = $callback[0]();
39 $callback[1] ??= '__invoke';
40 }
41 $value = $callback(...$arguments);
42 $callback = !\is_scalar($value) && !$value instanceof \Stringable ? self::getPrettyName($callback) : 'callable';
43 $arguments = null;
44 }
45
46 return $value ?? '';
47 };
48
49 return $lazyString;
50 }
51
52 public static function fromStringable(string|int|float|bool|\Stringable $value): static
53 {
54 if (\is_object($value)) {
55 return static::fromCallable($value->__toString(...));
56 }
57
58 $lazyString = new static();
59 $lazyString->value = (string) $value;
60
61 return $lazyString;
62 }
63
64 /**
65 * Tells whether the provided value can be cast to string.
66 */
67 final public static function isStringable(mixed $value): bool
68 {
69 return \is_string($value) || $value instanceof \Stringable || \is_scalar($value);
70 }
71
72 /**
73 * Casts scalars and stringable objects to strings.
74 *
75 * @throws \TypeError When the provided value is not stringable
76 */
77 final public static function resolve(\Stringable|string|int|float|bool $value): string
78 {
79 return $value;
80 }
81
82 public function __toString(): string
83 {
84 if (\is_string($this->value)) {
85 return $this->value;
86 }
87
88 try {
89 return $this->value = ($this->value)();
90 } catch (\Throwable $e) {
91 if (\TypeError::class === $e::class && __FILE__ === $e->getFile()) {
92 $type = explode(', ', $e->getMessage());
93 $type = substr(array_pop($type), 0, -\strlen(' returned'));
94 $r = new \ReflectionFunction($this->value);
95 $callback = $r->getStaticVariables()['callback'];
96
97 $e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
98 }
99
100 throw $e;
101 }
102 }
103
104 public function __sleep(): array
105 {
106 $this->__toString();
107
108 return ['value'];
109 }
110
111 public function jsonSerialize(): string
112 {
113 return $this->__toString();
114 }
115
116 private function __construct()
117 {
118 }
119
120 private static function getPrettyName(callable $callback): string
121 {
122 if (\is_string($callback)) {
123 return $callback;
124 }
125
126 if (\is_array($callback)) {
127 $class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0];
128 $method = $callback[1];
129 } elseif ($callback instanceof \Closure) {
130 $r = new \ReflectionFunction($callback);
131
132 if ($r->isAnonymous() || !$class = $r->getClosureCalledClass()) {
133 return $r->name;
134 }
135
136 $class = $class->name;
137 $method = $r->name;
138 } else {
139 $class = get_debug_type($callback);
140 $method = '__invoke';
141 }
142
143 return $class.'::'.$method;
144 }
145}
diff --git a/vendor/symfony/string/README.md b/vendor/symfony/string/README.md
new file mode 100644
index 0000000..9c7e1e1
--- /dev/null
+++ b/vendor/symfony/string/README.md
@@ -0,0 +1,14 @@
1String Component
2================
3
4The String component provides an object-oriented API to strings and deals
5with bytes, UTF-8 code points and grapheme clusters in a unified way.
6
7Resources
8---------
9
10 * [Documentation](https://symfony.com/doc/current/components/string.html)
11 * [Contributing](https://symfony.com/doc/current/contributing/index.html)
12 * [Report issues](https://github.com/symfony/symfony/issues) and
13 [send Pull Requests](https://github.com/symfony/symfony/pulls)
14 in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/string/Resources/data/wcswidth_table_wide.php b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php
new file mode 100644
index 0000000..8314c8f
--- /dev/null
+++ b/vendor/symfony/string/Resources/data/wcswidth_table_wide.php
@@ -0,0 +1,1155 @@
1<?php
2
3/*
4 * This file has been auto-generated by the Symfony String Component for internal use.
5 *
6 * Unicode version: 15.1.0
7 * Date: 2023-09-13T11:47:12+00:00
8 */
9
10return [
11 [
12 4352,
13 4447,
14 ],
15 [
16 8986,
17 8987,
18 ],
19 [
20 9001,
21 9001,
22 ],
23 [
24 9002,
25 9002,
26 ],
27 [
28 9193,
29 9196,
30 ],
31 [
32 9200,
33 9200,
34 ],
35 [
36 9203,
37 9203,
38 ],
39 [
40 9725,
41 9726,
42 ],
43 [
44 9748,
45 9749,
46 ],
47 [
48 9800,
49 9811,
50 ],
51 [
52 9855,
53 9855,
54 ],
55 [
56 9875,
57 9875,
58 ],
59 [
60 9889,
61 9889,
62 ],
63 [
64 9898,
65 9899,
66 ],
67 [
68 9917,
69 9918,
70 ],
71 [
72 9924,
73 9925,
74 ],
75 [
76 9934,
77 9934,
78 ],
79 [
80 9940,
81 9940,
82 ],
83 [
84 9962,
85 9962,
86 ],
87 [
88 9970,
89 9971,
90 ],
91 [
92 9973,
93 9973,
94 ],
95 [
96 9978,
97 9978,
98 ],
99 [
100 9981,
101 9981,
102 ],
103 [
104 9989,
105 9989,
106 ],
107 [
108 9994,
109 9995,
110 ],
111 [
112 10024,
113 10024,
114 ],
115 [
116 10060,
117 10060,
118 ],
119 [
120 10062,
121 10062,
122 ],
123 [
124 10067,
125 10069,
126 ],
127 [
128 10071,
129 10071,
130 ],
131 [
132 10133,
133 10135,
134 ],
135 [
136 10160,
137 10160,
138 ],
139 [
140 10175,
141 10175,
142 ],
143 [
144 11035,
145 11036,
146 ],
147 [
148 11088,
149 11088,
150 ],
151 [
152 11093,
153 11093,
154 ],
155 [
156 11904,
157 11929,
158 ],
159 [
160 11931,
161 12019,
162 ],
163 [
164 12032,
165 12245,
166 ],
167 [
168 12272,
169 12287,
170 ],
171 [
172 12288,
173 12288,
174 ],
175 [
176 12289,
177 12291,
178 ],
179 [
180 12292,
181 12292,
182 ],
183 [
184 12293,
185 12293,
186 ],
187 [
188 12294,
189 12294,
190 ],
191 [
192 12295,
193 12295,
194 ],
195 [
196 12296,
197 12296,
198 ],
199 [
200 12297,
201 12297,
202 ],
203 [
204 12298,
205 12298,
206 ],
207 [
208 12299,
209 12299,
210 ],
211 [
212 12300,
213 12300,
214 ],
215 [
216 12301,
217 12301,
218 ],
219 [
220 12302,
221 12302,
222 ],
223 [
224 12303,
225 12303,
226 ],
227 [
228 12304,
229 12304,
230 ],
231 [
232 12305,
233 12305,
234 ],
235 [
236 12306,
237 12307,
238 ],
239 [
240 12308,
241 12308,
242 ],
243 [
244 12309,
245 12309,
246 ],
247 [
248 12310,
249 12310,
250 ],
251 [
252 12311,
253 12311,
254 ],
255 [
256 12312,
257 12312,
258 ],
259 [
260 12313,
261 12313,
262 ],
263 [
264 12314,
265 12314,
266 ],
267 [
268 12315,
269 12315,
270 ],
271 [
272 12316,
273 12316,
274 ],
275 [
276 12317,
277 12317,
278 ],
279 [
280 12318,
281 12319,
282 ],
283 [
284 12320,
285 12320,
286 ],
287 [
288 12321,
289 12329,
290 ],
291 [
292 12330,
293 12333,
294 ],
295 [
296 12334,
297 12335,
298 ],
299 [
300 12336,
301 12336,
302 ],
303 [
304 12337,
305 12341,
306 ],
307 [
308 12342,
309 12343,
310 ],
311 [
312 12344,
313 12346,
314 ],
315 [
316 12347,
317 12347,
318 ],
319 [
320 12348,
321 12348,
322 ],
323 [
324 12349,
325 12349,
326 ],
327 [
328 12350,
329 12350,
330 ],
331 [
332 12353,
333 12438,
334 ],
335 [
336 12441,
337 12442,
338 ],
339 [
340 12443,
341 12444,
342 ],
343 [
344 12445,
345 12446,
346 ],
347 [
348 12447,
349 12447,
350 ],
351 [
352 12448,
353 12448,
354 ],
355 [
356 12449,
357 12538,
358 ],
359 [
360 12539,
361 12539,
362 ],
363 [
364 12540,
365 12542,
366 ],
367 [
368 12543,
369 12543,
370 ],
371 [
372 12549,
373 12591,
374 ],
375 [
376 12593,
377 12686,
378 ],
379 [
380 12688,
381 12689,
382 ],
383 [
384 12690,
385 12693,
386 ],
387 [
388 12694,
389 12703,
390 ],
391 [
392 12704,
393 12735,
394 ],
395 [
396 12736,
397 12771,
398 ],
399 [
400 12783,
401 12783,
402 ],
403 [
404 12784,
405 12799,
406 ],
407 [
408 12800,
409 12830,
410 ],
411 [
412 12832,
413 12841,
414 ],
415 [
416 12842,
417 12871,
418 ],
419 [
420 12880,
421 12880,
422 ],
423 [
424 12881,
425 12895,
426 ],
427 [
428 12896,
429 12927,
430 ],
431 [
432 12928,
433 12937,
434 ],
435 [
436 12938,
437 12976,
438 ],
439 [
440 12977,
441 12991,
442 ],
443 [
444 12992,
445 13055,
446 ],
447 [
448 13056,
449 13311,
450 ],
451 [
452 13312,
453 19903,
454 ],
455 [
456 19968,
457 40959,
458 ],
459 [
460 40960,
461 40980,
462 ],
463 [
464 40981,
465 40981,
466 ],
467 [
468 40982,
469 42124,
470 ],
471 [
472 42128,
473 42182,
474 ],
475 [
476 43360,
477 43388,
478 ],
479 [
480 44032,
481 55203,
482 ],
483 [
484 63744,
485 64109,
486 ],
487 [
488 64110,
489 64111,
490 ],
491 [
492 64112,
493 64217,
494 ],
495 [
496 64218,
497 64255,
498 ],
499 [
500 65040,
501 65046,
502 ],
503 [
504 65047,
505 65047,
506 ],
507 [
508 65048,
509 65048,
510 ],
511 [
512 65049,
513 65049,
514 ],
515 [
516 65072,
517 65072,
518 ],
519 [
520 65073,
521 65074,
522 ],
523 [
524 65075,
525 65076,
526 ],
527 [
528 65077,
529 65077,
530 ],
531 [
532 65078,
533 65078,
534 ],
535 [
536 65079,
537 65079,
538 ],
539 [
540 65080,
541 65080,
542 ],
543 [
544 65081,
545 65081,
546 ],
547 [
548 65082,
549 65082,
550 ],
551 [
552 65083,
553 65083,
554 ],
555 [
556 65084,
557 65084,
558 ],
559 [
560 65085,
561 65085,
562 ],
563 [
564 65086,
565 65086,
566 ],
567 [
568 65087,
569 65087,
570 ],
571 [
572 65088,
573 65088,
574 ],
575 [
576 65089,
577 65089,
578 ],
579 [
580 65090,
581 65090,
582 ],
583 [
584 65091,
585 65091,
586 ],
587 [
588 65092,
589 65092,
590 ],
591 [
592 65093,
593 65094,
594 ],
595 [
596 65095,
597 65095,
598 ],
599 [
600 65096,
601 65096,
602 ],
603 [
604 65097,
605 65100,
606 ],
607 [
608 65101,
609 65103,
610 ],
611 [
612 65104,
613 65106,
614 ],
615 [
616 65108,
617 65111,
618 ],
619 [
620 65112,
621 65112,
622 ],
623 [
624 65113,
625 65113,
626 ],
627 [
628 65114,
629 65114,
630 ],
631 [
632 65115,
633 65115,
634 ],
635 [
636 65116,
637 65116,
638 ],
639 [
640 65117,
641 65117,
642 ],
643 [
644 65118,
645 65118,
646 ],
647 [
648 65119,
649 65121,
650 ],
651 [
652 65122,
653 65122,
654 ],
655 [
656 65123,
657 65123,
658 ],
659 [
660 65124,
661 65126,
662 ],
663 [
664 65128,
665 65128,
666 ],
667 [
668 65129,
669 65129,
670 ],
671 [
672 65130,
673 65131,
674 ],
675 [
676 65281,
677 65283,
678 ],
679 [
680 65284,
681 65284,
682 ],
683 [
684 65285,
685 65287,
686 ],
687 [
688 65288,
689 65288,
690 ],
691 [
692 65289,
693 65289,
694 ],
695 [
696 65290,
697 65290,
698 ],
699 [
700 65291,
701 65291,
702 ],
703 [
704 65292,
705 65292,
706 ],
707 [
708 65293,
709 65293,
710 ],
711 [
712 65294,
713 65295,
714 ],
715 [
716 65296,
717 65305,
718 ],
719 [
720 65306,
721 65307,
722 ],
723 [
724 65308,
725 65310,
726 ],
727 [
728 65311,
729 65312,
730 ],
731 [
732 65313,
733 65338,
734 ],
735 [
736 65339,
737 65339,
738 ],
739 [
740 65340,
741 65340,
742 ],
743 [
744 65341,
745 65341,
746 ],
747 [
748 65342,
749 65342,
750 ],
751 [
752 65343,
753 65343,
754 ],
755 [
756 65344,
757 65344,
758 ],
759 [
760 65345,
761 65370,
762 ],
763 [
764 65371,
765 65371,
766 ],
767 [
768 65372,
769 65372,
770 ],
771 [
772 65373,
773 65373,
774 ],
775 [
776 65374,
777 65374,
778 ],
779 [
780 65375,
781 65375,
782 ],
783 [
784 65376,
785 65376,
786 ],
787 [
788 65504,
789 65505,
790 ],
791 [
792 65506,
793 65506,
794 ],
795 [
796 65507,
797 65507,
798 ],
799 [
800 65508,
801 65508,
802 ],
803 [
804 65509,
805 65510,
806 ],
807 [
808 94176,
809 94177,
810 ],
811 [
812 94178,
813 94178,
814 ],
815 [
816 94179,
817 94179,
818 ],
819 [
820 94180,
821 94180,
822 ],
823 [
824 94192,
825 94193,
826 ],
827 [
828 94208,
829 100343,
830 ],
831 [
832 100352,
833 101119,
834 ],
835 [
836 101120,
837 101589,
838 ],
839 [
840 101632,
841 101640,
842 ],
843 [
844 110576,
845 110579,
846 ],
847 [
848 110581,
849 110587,
850 ],
851 [
852 110589,
853 110590,
854 ],
855 [
856 110592,
857 110847,
858 ],
859 [
860 110848,
861 110882,
862 ],
863 [
864 110898,
865 110898,
866 ],
867 [
868 110928,
869 110930,
870 ],
871 [
872 110933,
873 110933,
874 ],
875 [
876 110948,
877 110951,
878 ],
879 [
880 110960,
881 111355,
882 ],
883 [
884 126980,
885 126980,
886 ],
887 [
888 127183,
889 127183,
890 ],
891 [
892 127374,
893 127374,
894 ],
895 [
896 127377,
897 127386,
898 ],
899 [
900 127488,
901 127490,
902 ],
903 [
904 127504,
905 127547,
906 ],
907 [
908 127552,
909 127560,
910 ],
911 [
912 127568,
913 127569,
914 ],
915 [
916 127584,
917 127589,
918 ],
919 [
920 127744,
921 127776,
922 ],
923 [
924 127789,
925 127797,
926 ],
927 [
928 127799,
929 127868,
930 ],
931 [
932 127870,
933 127891,
934 ],
935 [
936 127904,
937 127946,
938 ],
939 [
940 127951,
941 127955,
942 ],
943 [
944 127968,
945 127984,
946 ],
947 [
948 127988,
949 127988,
950 ],
951 [
952 127992,
953 127994,
954 ],
955 [
956 127995,
957 127999,
958 ],
959 [
960 128000,
961 128062,
962 ],
963 [
964 128064,
965 128064,
966 ],
967 [
968 128066,
969 128252,
970 ],
971 [
972 128255,
973 128317,
974 ],
975 [
976 128331,
977 128334,
978 ],
979 [
980 128336,
981 128359,
982 ],
983 [
984 128378,
985 128378,
986 ],
987 [
988 128405,
989 128406,
990 ],
991 [
992 128420,
993 128420,
994 ],
995 [
996 128507,
997 128511,
998 ],
999 [
1000 128512,
1001 128591,
1002 ],
1003 [
1004 128640,
1005 128709,
1006 ],
1007 [
1008 128716,
1009 128716,
1010 ],
1011 [
1012 128720,
1013 128722,
1014 ],
1015 [
1016 128725,
1017 128727,
1018 ],
1019 [
1020 128732,
1021 128735,
1022 ],
1023 [
1024 128747,
1025 128748,
1026 ],
1027 [
1028 128756,
1029 128764,
1030 ],
1031 [
1032 128992,
1033 129003,
1034 ],
1035 [
1036 129008,
1037 129008,
1038 ],
1039 [
1040 129292,
1041 129338,
1042 ],
1043 [
1044 129340,
1045 129349,
1046 ],
1047 [
1048 129351,
1049 129535,
1050 ],
1051 [
1052 129648,
1053 129660,
1054 ],
1055 [
1056 129664,
1057 129672,
1058 ],
1059 [
1060 129680,
1061 129725,
1062 ],
1063 [
1064 129727,
1065 129733,
1066 ],
1067 [
1068 129742,
1069 129755,
1070 ],
1071 [
1072 129760,
1073 129768,
1074 ],
1075 [
1076 129776,
1077 129784,
1078 ],
1079 [
1080 131072,
1081 173791,
1082 ],
1083 [
1084 173792,
1085 173823,
1086 ],
1087 [
1088 173824,
1089 177977,
1090 ],
1091 [
1092 177978,
1093 177983,
1094 ],
1095 [
1096 177984,
1097 178205,
1098 ],
1099 [
1100 178206,
1101 178207,
1102 ],
1103 [
1104 178208,
1105 183969,
1106 ],
1107 [
1108 183970,
1109 183983,
1110 ],
1111 [
1112 183984,
1113 191456,
1114 ],
1115 [
1116 191457,
1117 191471,
1118 ],
1119 [
1120 191472,
1121 192093,
1122 ],
1123 [
1124 192094,
1125 194559,
1126 ],
1127 [
1128 194560,
1129 195101,
1130 ],
1131 [
1132 195102,
1133 195103,
1134 ],
1135 [
1136 195104,
1137 196605,
1138 ],
1139 [
1140 196608,
1141 201546,
1142 ],
1143 [
1144 201547,
1145 201551,
1146 ],
1147 [
1148 201552,
1149 205743,
1150 ],
1151 [
1152 205744,
1153 262141,
1154 ],
1155];
diff --git a/vendor/symfony/string/Resources/data/wcswidth_table_zero.php b/vendor/symfony/string/Resources/data/wcswidth_table_zero.php
new file mode 100644
index 0000000..e5b26a2
--- /dev/null
+++ b/vendor/symfony/string/Resources/data/wcswidth_table_zero.php
@@ -0,0 +1,1415 @@
1<?php
2
3/*
4 * This file has been auto-generated by the Symfony String Component for internal use.
5 *
6 * Unicode version: 15.1.0
7 * Date: 2023-09-13T11:47:13+00:00
8 */
9
10return [
11 [
12 768,
13 879,
14 ],
15 [
16 1155,
17 1159,
18 ],
19 [
20 1160,
21 1161,
22 ],
23 [
24 1425,
25 1469,
26 ],
27 [
28 1471,
29 1471,
30 ],
31 [
32 1473,
33 1474,
34 ],
35 [
36 1476,
37 1477,
38 ],
39 [
40 1479,
41 1479,
42 ],
43 [
44 1552,
45 1562,
46 ],
47 [
48 1611,
49 1631,
50 ],
51 [
52 1648,
53 1648,
54 ],
55 [
56 1750,
57 1756,
58 ],
59 [
60 1759,
61 1764,
62 ],
63 [
64 1767,
65 1768,
66 ],
67 [
68 1770,
69 1773,
70 ],
71 [
72 1809,
73 1809,
74 ],
75 [
76 1840,
77 1866,
78 ],
79 [
80 1958,
81 1968,
82 ],
83 [
84 2027,
85 2035,
86 ],
87 [
88 2045,
89 2045,
90 ],
91 [
92 2070,
93 2073,
94 ],
95 [
96 2075,
97 2083,
98 ],
99 [
100 2085,
101 2087,
102 ],
103 [
104 2089,
105 2093,
106 ],
107 [
108 2137,
109 2139,
110 ],
111 [
112 2200,
113 2207,
114 ],
115 [
116 2250,
117 2273,
118 ],
119 [
120 2275,
121 2306,
122 ],
123 [
124 2362,
125 2362,
126 ],
127 [
128 2364,
129 2364,
130 ],
131 [
132 2369,
133 2376,
134 ],
135 [
136 2381,
137 2381,
138 ],
139 [
140 2385,
141 2391,
142 ],
143 [
144 2402,
145 2403,
146 ],
147 [
148 2433,
149 2433,
150 ],
151 [
152 2492,
153 2492,
154 ],
155 [
156 2497,
157 2500,
158 ],
159 [
160 2509,
161 2509,
162 ],
163 [
164 2530,
165 2531,
166 ],
167 [
168 2558,
169 2558,
170 ],
171 [
172 2561,
173 2562,
174 ],
175 [
176 2620,
177 2620,
178 ],
179 [
180 2625,
181 2626,
182 ],
183 [
184 2631,
185 2632,
186 ],
187 [
188 2635,
189 2637,
190 ],
191 [
192 2641,
193 2641,
194 ],
195 [
196 2672,
197 2673,
198 ],
199 [
200 2677,
201 2677,
202 ],
203 [
204 2689,
205 2690,
206 ],
207 [
208 2748,
209 2748,
210 ],
211 [
212 2753,
213 2757,
214 ],
215 [
216 2759,
217 2760,
218 ],
219 [
220 2765,
221 2765,
222 ],
223 [
224 2786,
225 2787,
226 ],
227 [
228 2810,
229 2815,
230 ],
231 [
232 2817,
233 2817,
234 ],
235 [
236 2876,
237 2876,
238 ],
239 [
240 2879,
241 2879,
242 ],
243 [
244 2881,
245 2884,
246 ],
247 [
248 2893,
249 2893,
250 ],
251 [
252 2901,
253 2902,
254 ],
255 [
256 2914,
257 2915,
258 ],
259 [
260 2946,
261 2946,
262 ],
263 [
264 3008,
265 3008,
266 ],
267 [
268 3021,
269 3021,
270 ],
271 [
272 3072,
273 3072,
274 ],
275 [
276 3076,
277 3076,
278 ],
279 [
280 3132,
281 3132,
282 ],
283 [
284 3134,
285 3136,
286 ],
287 [
288 3142,
289 3144,
290 ],
291 [
292 3146,
293 3149,
294 ],
295 [
296 3157,
297 3158,
298 ],
299 [
300 3170,
301 3171,
302 ],
303 [
304 3201,
305 3201,
306 ],
307 [
308 3260,
309 3260,
310 ],
311 [
312 3263,
313 3263,
314 ],
315 [
316 3270,
317 3270,
318 ],
319 [
320 3276,
321 3277,
322 ],
323 [
324 3298,
325 3299,
326 ],
327 [
328 3328,
329 3329,
330 ],
331 [
332 3387,
333 3388,
334 ],
335 [
336 3393,
337 3396,
338 ],
339 [
340 3405,
341 3405,
342 ],
343 [
344 3426,
345 3427,
346 ],
347 [
348 3457,
349 3457,
350 ],
351 [
352 3530,
353 3530,
354 ],
355 [
356 3538,
357 3540,
358 ],
359 [
360 3542,
361 3542,
362 ],
363 [
364 3633,
365 3633,
366 ],
367 [
368 3636,
369 3642,
370 ],
371 [
372 3655,
373 3662,
374 ],
375 [
376 3761,
377 3761,
378 ],
379 [
380 3764,
381 3772,
382 ],
383 [
384 3784,
385 3790,
386 ],
387 [
388 3864,
389 3865,
390 ],
391 [
392 3893,
393 3893,
394 ],
395 [
396 3895,
397 3895,
398 ],
399 [
400 3897,
401 3897,
402 ],
403 [
404 3953,
405 3966,
406 ],
407 [
408 3968,
409 3972,
410 ],
411 [
412 3974,
413 3975,
414 ],
415 [
416 3981,
417 3991,
418 ],
419 [
420 3993,
421 4028,
422 ],
423 [
424 4038,
425 4038,
426 ],
427 [
428 4141,
429 4144,
430 ],
431 [
432 4146,
433 4151,
434 ],
435 [
436 4153,
437 4154,
438 ],
439 [
440 4157,
441 4158,
442 ],
443 [
444 4184,
445 4185,
446 ],
447 [
448 4190,
449 4192,
450 ],
451 [
452 4209,
453 4212,
454 ],
455 [
456 4226,
457 4226,
458 ],
459 [
460 4229,
461 4230,
462 ],
463 [
464 4237,
465 4237,
466 ],
467 [
468 4253,
469 4253,
470 ],
471 [
472 4957,
473 4959,
474 ],
475 [
476 5906,
477 5908,
478 ],
479 [
480 5938,
481 5939,
482 ],
483 [
484 5970,
485 5971,
486 ],
487 [
488 6002,
489 6003,
490 ],
491 [
492 6068,
493 6069,
494 ],
495 [
496 6071,
497 6077,
498 ],
499 [
500 6086,
501 6086,
502 ],
503 [
504 6089,
505 6099,
506 ],
507 [
508 6109,
509 6109,
510 ],
511 [
512 6155,
513 6157,
514 ],
515 [
516 6159,
517 6159,
518 ],
519 [
520 6277,
521 6278,
522 ],
523 [
524 6313,
525 6313,
526 ],
527 [
528 6432,
529 6434,
530 ],
531 [
532 6439,
533 6440,
534 ],
535 [
536 6450,
537 6450,
538 ],
539 [
540 6457,
541 6459,
542 ],
543 [
544 6679,
545 6680,
546 ],
547 [
548 6683,
549 6683,
550 ],
551 [
552 6742,
553 6742,
554 ],
555 [
556 6744,
557 6750,
558 ],
559 [
560 6752,
561 6752,
562 ],
563 [
564 6754,
565 6754,
566 ],
567 [
568 6757,
569 6764,
570 ],
571 [
572 6771,
573 6780,
574 ],
575 [
576 6783,
577 6783,
578 ],
579 [
580 6832,
581 6845,
582 ],
583 [
584 6846,
585 6846,
586 ],
587 [
588 6847,
589 6862,
590 ],
591 [
592 6912,
593 6915,
594 ],
595 [
596 6964,
597 6964,
598 ],
599 [
600 6966,
601 6970,
602 ],
603 [
604 6972,
605 6972,
606 ],
607 [
608 6978,
609 6978,
610 ],
611 [
612 7019,
613 7027,
614 ],
615 [
616 7040,
617 7041,
618 ],
619 [
620 7074,
621 7077,
622 ],
623 [
624 7080,
625 7081,
626 ],
627 [
628 7083,
629 7085,
630 ],
631 [
632 7142,
633 7142,
634 ],
635 [
636 7144,
637 7145,
638 ],
639 [
640 7149,
641 7149,
642 ],
643 [
644 7151,
645 7153,
646 ],
647 [
648 7212,
649 7219,
650 ],
651 [
652 7222,
653 7223,
654 ],
655 [
656 7376,
657 7378,
658 ],
659 [
660 7380,
661 7392,
662 ],
663 [
664 7394,
665 7400,
666 ],
667 [
668 7405,
669 7405,
670 ],
671 [
672 7412,
673 7412,
674 ],
675 [
676 7416,
677 7417,
678 ],
679 [
680 7616,
681 7679,
682 ],
683 [
684 8400,
685 8412,
686 ],
687 [
688 8413,
689 8416,
690 ],
691 [
692 8417,
693 8417,
694 ],
695 [
696 8418,
697 8420,
698 ],
699 [
700 8421,
701 8432,
702 ],
703 [
704 11503,
705 11505,
706 ],
707 [
708 11647,
709 11647,
710 ],
711 [
712 11744,
713 11775,
714 ],
715 [
716 12330,
717 12333,
718 ],
719 [
720 12441,
721 12442,
722 ],
723 [
724 42607,
725 42607,
726 ],
727 [
728 42608,
729 42610,
730 ],
731 [
732 42612,
733 42621,
734 ],
735 [
736 42654,
737 42655,
738 ],
739 [
740 42736,
741 42737,
742 ],
743 [
744 43010,
745 43010,
746 ],
747 [
748 43014,
749 43014,
750 ],
751 [
752 43019,
753 43019,
754 ],
755 [
756 43045,
757 43046,
758 ],
759 [
760 43052,
761 43052,
762 ],
763 [
764 43204,
765 43205,
766 ],
767 [
768 43232,
769 43249,
770 ],
771 [
772 43263,
773 43263,
774 ],
775 [
776 43302,
777 43309,
778 ],
779 [
780 43335,
781 43345,
782 ],
783 [
784 43392,
785 43394,
786 ],
787 [
788 43443,
789 43443,
790 ],
791 [
792 43446,
793 43449,
794 ],
795 [
796 43452,
797 43453,
798 ],
799 [
800 43493,
801 43493,
802 ],
803 [
804 43561,
805 43566,
806 ],
807 [
808 43569,
809 43570,
810 ],
811 [
812 43573,
813 43574,
814 ],
815 [
816 43587,
817 43587,
818 ],
819 [
820 43596,
821 43596,
822 ],
823 [
824 43644,
825 43644,
826 ],
827 [
828 43696,
829 43696,
830 ],
831 [
832 43698,
833 43700,
834 ],
835 [
836 43703,
837 43704,
838 ],
839 [
840 43710,
841 43711,
842 ],
843 [
844 43713,
845 43713,
846 ],
847 [
848 43756,
849 43757,
850 ],
851 [
852 43766,
853 43766,
854 ],
855 [
856 44005,
857 44005,
858 ],
859 [
860 44008,
861 44008,
862 ],
863 [
864 44013,
865 44013,
866 ],
867 [
868 64286,
869 64286,
870 ],
871 [
872 65024,
873 65039,
874 ],
875 [
876 65056,
877 65071,
878 ],
879 [
880 66045,
881 66045,
882 ],
883 [
884 66272,
885 66272,
886 ],
887 [
888 66422,
889 66426,
890 ],
891 [
892 68097,
893 68099,
894 ],
895 [
896 68101,
897 68102,
898 ],
899 [
900 68108,
901 68111,
902 ],
903 [
904 68152,
905 68154,
906 ],
907 [
908 68159,
909 68159,
910 ],
911 [
912 68325,
913 68326,
914 ],
915 [
916 68900,
917 68903,
918 ],
919 [
920 69291,
921 69292,
922 ],
923 [
924 69373,
925 69375,
926 ],
927 [
928 69446,
929 69456,
930 ],
931 [
932 69506,
933 69509,
934 ],
935 [
936 69633,
937 69633,
938 ],
939 [
940 69688,
941 69702,
942 ],
943 [
944 69744,
945 69744,
946 ],
947 [
948 69747,
949 69748,
950 ],
951 [
952 69759,
953 69761,
954 ],
955 [
956 69811,
957 69814,
958 ],
959 [
960 69817,
961 69818,
962 ],
963 [
964 69826,
965 69826,
966 ],
967 [
968 69888,
969 69890,
970 ],
971 [
972 69927,
973 69931,
974 ],
975 [
976 69933,
977 69940,
978 ],
979 [
980 70003,
981 70003,
982 ],
983 [
984 70016,
985 70017,
986 ],
987 [
988 70070,
989 70078,
990 ],
991 [
992 70089,
993 70092,
994 ],
995 [
996 70095,
997 70095,
998 ],
999 [
1000 70191,
1001 70193,
1002 ],
1003 [
1004 70196,
1005 70196,
1006 ],
1007 [
1008 70198,
1009 70199,
1010 ],
1011 [
1012 70206,
1013 70206,
1014 ],
1015 [
1016 70209,
1017 70209,
1018 ],
1019 [
1020 70367,
1021 70367,
1022 ],
1023 [
1024 70371,
1025 70378,
1026 ],
1027 [
1028 70400,
1029 70401,
1030 ],
1031 [
1032 70459,
1033 70460,
1034 ],
1035 [
1036 70464,
1037 70464,
1038 ],
1039 [
1040 70502,
1041 70508,
1042 ],
1043 [
1044 70512,
1045 70516,
1046 ],
1047 [
1048 70712,
1049 70719,
1050 ],
1051 [
1052 70722,
1053 70724,
1054 ],
1055 [
1056 70726,
1057 70726,
1058 ],
1059 [
1060 70750,
1061 70750,
1062 ],
1063 [
1064 70835,
1065 70840,
1066 ],
1067 [
1068 70842,
1069 70842,
1070 ],
1071 [
1072 70847,
1073 70848,
1074 ],
1075 [
1076 70850,
1077 70851,
1078 ],
1079 [
1080 71090,
1081 71093,
1082 ],
1083 [
1084 71100,
1085 71101,
1086 ],
1087 [
1088 71103,
1089 71104,
1090 ],
1091 [
1092 71132,
1093 71133,
1094 ],
1095 [
1096 71219,
1097 71226,
1098 ],
1099 [
1100 71229,
1101 71229,
1102 ],
1103 [
1104 71231,
1105 71232,
1106 ],
1107 [
1108 71339,
1109 71339,
1110 ],
1111 [
1112 71341,
1113 71341,
1114 ],
1115 [
1116 71344,
1117 71349,
1118 ],
1119 [
1120 71351,
1121 71351,
1122 ],
1123 [
1124 71453,
1125 71455,
1126 ],
1127 [
1128 71458,
1129 71461,
1130 ],
1131 [
1132 71463,
1133 71467,
1134 ],
1135 [
1136 71727,
1137 71735,
1138 ],
1139 [
1140 71737,
1141 71738,
1142 ],
1143 [
1144 71995,
1145 71996,
1146 ],
1147 [
1148 71998,
1149 71998,
1150 ],
1151 [
1152 72003,
1153 72003,
1154 ],
1155 [
1156 72148,
1157 72151,
1158 ],
1159 [
1160 72154,
1161 72155,
1162 ],
1163 [
1164 72160,
1165 72160,
1166 ],
1167 [
1168 72193,
1169 72202,
1170 ],
1171 [
1172 72243,
1173 72248,
1174 ],
1175 [
1176 72251,
1177 72254,
1178 ],
1179 [
1180 72263,
1181 72263,
1182 ],
1183 [
1184 72273,
1185 72278,
1186 ],
1187 [
1188 72281,
1189 72283,
1190 ],
1191 [
1192 72330,
1193 72342,
1194 ],
1195 [
1196 72344,
1197 72345,
1198 ],
1199 [
1200 72752,
1201 72758,
1202 ],
1203 [
1204 72760,
1205 72765,
1206 ],
1207 [
1208 72767,
1209 72767,
1210 ],
1211 [
1212 72850,
1213 72871,
1214 ],
1215 [
1216 72874,
1217 72880,
1218 ],
1219 [
1220 72882,
1221 72883,
1222 ],
1223 [
1224 72885,
1225 72886,
1226 ],
1227 [
1228 73009,
1229 73014,
1230 ],
1231 [
1232 73018,
1233 73018,
1234 ],
1235 [
1236 73020,
1237 73021,
1238 ],
1239 [
1240 73023,
1241 73029,
1242 ],
1243 [
1244 73031,
1245 73031,
1246 ],
1247 [
1248 73104,
1249 73105,
1250 ],
1251 [
1252 73109,
1253 73109,
1254 ],
1255 [
1256 73111,
1257 73111,
1258 ],
1259 [
1260 73459,
1261 73460,
1262 ],
1263 [
1264 73472,
1265 73473,
1266 ],
1267 [
1268 73526,
1269 73530,
1270 ],
1271 [
1272 73536,
1273 73536,
1274 ],
1275 [
1276 73538,
1277 73538,
1278 ],
1279 [
1280 78912,
1281 78912,
1282 ],
1283 [
1284 78919,
1285 78933,
1286 ],
1287 [
1288 92912,
1289 92916,
1290 ],
1291 [
1292 92976,
1293 92982,
1294 ],
1295 [
1296 94031,
1297 94031,
1298 ],
1299 [
1300 94095,
1301 94098,
1302 ],
1303 [
1304 94180,
1305 94180,
1306 ],
1307 [
1308 113821,
1309 113822,
1310 ],
1311 [
1312 118528,
1313 118573,
1314 ],
1315 [
1316 118576,
1317 118598,
1318 ],
1319 [
1320 119143,
1321 119145,
1322 ],
1323 [
1324 119163,
1325 119170,
1326 ],
1327 [
1328 119173,
1329 119179,
1330 ],
1331 [
1332 119210,
1333 119213,
1334 ],
1335 [
1336 119362,
1337 119364,
1338 ],
1339 [
1340 121344,
1341 121398,
1342 ],
1343 [
1344 121403,
1345 121452,
1346 ],
1347 [
1348 121461,
1349 121461,
1350 ],
1351 [
1352 121476,
1353 121476,
1354 ],
1355 [
1356 121499,
1357 121503,
1358 ],
1359 [
1360 121505,
1361 121519,
1362 ],
1363 [
1364 122880,
1365 122886,
1366 ],
1367 [
1368 122888,
1369 122904,
1370 ],
1371 [
1372 122907,
1373 122913,
1374 ],
1375 [
1376 122915,
1377 122916,
1378 ],
1379 [
1380 122918,
1381 122922,
1382 ],
1383 [
1384 123023,
1385 123023,
1386 ],
1387 [
1388 123184,
1389 123190,
1390 ],
1391 [
1392 123566,
1393 123566,
1394 ],
1395 [
1396 123628,
1397 123631,
1398 ],
1399 [
1400 124140,
1401 124143,
1402 ],
1403 [
1404 125136,
1405 125142,
1406 ],
1407 [
1408 125252,
1409 125258,
1410 ],
1411 [
1412 917760,
1413 917999,
1414 ],
1415];
diff --git a/vendor/symfony/string/Resources/functions.php b/vendor/symfony/string/Resources/functions.php
new file mode 100644
index 0000000..7a97040
--- /dev/null
+++ b/vendor/symfony/string/Resources/functions.php
@@ -0,0 +1,38 @@
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
12namespace Symfony\Component\String;
13
14if (!\function_exists(u::class)) {
15 function u(?string $string = ''): UnicodeString
16 {
17 return new UnicodeString($string ?? '');
18 }
19}
20
21if (!\function_exists(b::class)) {
22 function b(?string $string = ''): ByteString
23 {
24 return new ByteString($string ?? '');
25 }
26}
27
28if (!\function_exists(s::class)) {
29 /**
30 * @return UnicodeString|ByteString
31 */
32 function s(?string $string = ''): AbstractString
33 {
34 $string ??= '';
35
36 return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
37 }
38}
diff --git a/vendor/symfony/string/Slugger/AsciiSlugger.php b/vendor/symfony/string/Slugger/AsciiSlugger.php
new file mode 100644
index 0000000..d254532
--- /dev/null
+++ b/vendor/symfony/string/Slugger/AsciiSlugger.php
@@ -0,0 +1,207 @@
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
12namespace Symfony\Component\String\Slugger;
13
14use Symfony\Component\Emoji\EmojiTransliterator;
15use Symfony\Component\String\AbstractUnicodeString;
16use Symfony\Component\String\UnicodeString;
17use Symfony\Contracts\Translation\LocaleAwareInterface;
18
19if (!interface_exists(LocaleAwareInterface::class)) {
20 throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".');
21}
22
23/**
24 * @author Titouan Galopin <galopintitouan@gmail.com>
25 */
26class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
27{
28 private const LOCALE_TO_TRANSLITERATOR_ID = [
29 'am' => 'Amharic-Latin',
30 'ar' => 'Arabic-Latin',
31 'az' => 'Azerbaijani-Latin',
32 'be' => 'Belarusian-Latin',
33 'bg' => 'Bulgarian-Latin',
34 'bn' => 'Bengali-Latin',
35 'de' => 'de-ASCII',
36 'el' => 'Greek-Latin',
37 'fa' => 'Persian-Latin',
38 'he' => 'Hebrew-Latin',
39 'hy' => 'Armenian-Latin',
40 'ka' => 'Georgian-Latin',
41 'kk' => 'Kazakh-Latin',
42 'ky' => 'Kirghiz-Latin',
43 'ko' => 'Korean-Latin',
44 'mk' => 'Macedonian-Latin',
45 'mn' => 'Mongolian-Latin',
46 'or' => 'Oriya-Latin',
47 'ps' => 'Pashto-Latin',
48 'ru' => 'Russian-Latin',
49 'sr' => 'Serbian-Latin',
50 'sr_Cyrl' => 'Serbian-Latin',
51 'th' => 'Thai-Latin',
52 'tk' => 'Turkmen-Latin',
53 'uk' => 'Ukrainian-Latin',
54 'uz' => 'Uzbek-Latin',
55 'zh' => 'Han-Latin',
56 ];
57
58 private \Closure|array $symbolsMap = [
59 'en' => ['@' => 'at', '&' => 'and'],
60 ];
61 private bool|string $emoji = false;
62
63 /**
64 * Cache of transliterators per locale.
65 *
66 * @var \Transliterator[]
67 */
68 private array $transliterators = [];
69
70 public function __construct(
71 private ?string $defaultLocale = null,
72 array|\Closure|null $symbolsMap = null,
73 ) {
74 $this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
75 }
76
77 public function setLocale(string $locale): void
78 {
79 $this->defaultLocale = $locale;
80 }
81
82 public function getLocale(): string
83 {
84 return $this->defaultLocale;
85 }
86
87 /**
88 * @param bool|string $emoji true will use the same locale,
89 * false will disable emoji,
90 * and a string to use a specific locale
91 */
92 public function withEmoji(bool|string $emoji = true): static
93 {
94 if (false !== $emoji && !class_exists(EmojiTransliterator::class)) {
95 throw new \LogicException(sprintf('You cannot use the "%s()" method as the "symfony/emoji" package is not installed. Try running "composer require symfony/emoji".', __METHOD__));
96 }
97
98 $new = clone $this;
99 $new->emoji = $emoji;
100
101 return $new;
102 }
103
104 public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString
105 {
106 $locale ??= $this->defaultLocale;
107
108 $transliterator = [];
109 if ($locale && ('de' === $locale || str_starts_with($locale, 'de_'))) {
110 // Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
111 $transliterator = ['de-ASCII'];
112 } elseif (\function_exists('transliterator_transliterate') && $locale) {
113 $transliterator = (array) $this->createTransliterator($locale);
114 }
115
116 if ($emojiTransliterator = $this->createEmojiTransliterator($locale)) {
117 $transliterator[] = $emojiTransliterator;
118 }
119
120 if ($this->symbolsMap instanceof \Closure) {
121 // If the symbols map is passed as a closure, there is no need to fallback to the parent locale
122 // as the closure can just provide substitutions for all locales of interest.
123 $symbolsMap = $this->symbolsMap;
124 array_unshift($transliterator, static fn ($s) => $symbolsMap($s, $locale));
125 }
126
127 $unicodeString = (new UnicodeString($string))->ascii($transliterator);
128
129 if (\is_array($this->symbolsMap)) {
130 $map = null;
131 if (isset($this->symbolsMap[$locale])) {
132 $map = $this->symbolsMap[$locale];
133 } else {
134 $parent = self::getParentLocale($locale);
135 if ($parent && isset($this->symbolsMap[$parent])) {
136 $map = $this->symbolsMap[$parent];
137 }
138 }
139 if ($map) {
140 foreach ($map as $char => $replace) {
141 $unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
142 }
143 }
144 }
145
146 return $unicodeString
147 ->replaceMatches('/[^A-Za-z0-9]++/', $separator)
148 ->trim($separator)
149 ;
150 }
151
152 private function createTransliterator(string $locale): ?\Transliterator
153 {
154 if (\array_key_exists($locale, $this->transliterators)) {
155 return $this->transliterators[$locale];
156 }
157
158 // Exact locale supported, cache and return
159 if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) {
160 return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
161 }
162
163 // Locale not supported and no parent, fallback to any-latin
164 if (!$parent = self::getParentLocale($locale)) {
165 return $this->transliterators[$locale] = null;
166 }
167
168 // Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
169 if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
170 $transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
171 }
172
173 return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
174 }
175
176 private function createEmojiTransliterator(?string $locale): ?EmojiTransliterator
177 {
178 if (\is_string($this->emoji)) {
179 $locale = $this->emoji;
180 } elseif (!$this->emoji) {
181 return null;
182 }
183
184 while (null !== $locale) {
185 try {
186 return EmojiTransliterator::create("emoji-$locale");
187 } catch (\IntlException) {
188 $locale = self::getParentLocale($locale);
189 }
190 }
191
192 return null;
193 }
194
195 private static function getParentLocale(?string $locale): ?string
196 {
197 if (!$locale) {
198 return null;
199 }
200 if (false === $str = strrchr($locale, '_')) {
201 // no parent locale
202 return null;
203 }
204
205 return substr($locale, 0, -\strlen($str));
206 }
207}
diff --git a/vendor/symfony/string/Slugger/SluggerInterface.php b/vendor/symfony/string/Slugger/SluggerInterface.php
new file mode 100644
index 0000000..dd0d581
--- /dev/null
+++ b/vendor/symfony/string/Slugger/SluggerInterface.php
@@ -0,0 +1,27 @@
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
12namespace Symfony\Component\String\Slugger;
13
14use Symfony\Component\String\AbstractUnicodeString;
15
16/**
17 * Creates a URL-friendly slug from a given string.
18 *
19 * @author Titouan Galopin <galopintitouan@gmail.com>
20 */
21interface SluggerInterface
22{
23 /**
24 * Creates a slug for the given string and locale, using appropriate transliteration when needed.
25 */
26 public function slug(string $string, string $separator = '-', ?string $locale = null): AbstractUnicodeString;
27}
diff --git a/vendor/symfony/string/UnicodeString.php b/vendor/symfony/string/UnicodeString.php
new file mode 100644
index 0000000..4b16caf
--- /dev/null
+++ b/vendor/symfony/string/UnicodeString.php
@@ -0,0 +1,382 @@
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
12namespace Symfony\Component\String;
13
14use Symfony\Component\String\Exception\ExceptionInterface;
15use Symfony\Component\String\Exception\InvalidArgumentException;
16
17/**
18 * Represents a string of Unicode grapheme clusters encoded as UTF-8.
19 *
20 * A letter followed by combining characters (accents typically) form what Unicode defines
21 * as a grapheme cluster: a character as humans mean it in written texts. This class knows
22 * about the concept and won't split a letter apart from its combining accents. It also
23 * ensures all string comparisons happen on their canonically-composed representation,
24 * ignoring e.g. the order in which accents are listed when a letter has many of them.
25 *
26 * @see https://unicode.org/reports/tr15/
27 *
28 * @author Nicolas Grekas <p@tchwork.com>
29 * @author Hugo Hamon <hugohamon@neuf.fr>
30 *
31 * @throws ExceptionInterface
32 */
33class UnicodeString extends AbstractUnicodeString
34{
35 public function __construct(string $string = '')
36 {
37 if ('' === $string || normalizer_is_normalized($this->string = $string)) {
38 return;
39 }
40
41 if (false === $string = normalizer_normalize($string)) {
42 throw new InvalidArgumentException('Invalid UTF-8 string.');
43 }
44
45 $this->string = $string;
46 }
47
48 public function append(string ...$suffix): static
49 {
50 $str = clone $this;
51 $str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));
52
53 if (normalizer_is_normalized($str->string)) {
54 return $str;
55 }
56
57 if (false === $string = normalizer_normalize($str->string)) {
58 throw new InvalidArgumentException('Invalid UTF-8 string.');
59 }
60
61 $str->string = $string;
62
63 return $str;
64 }
65
66 public function chunk(int $length = 1): array
67 {
68 if (1 > $length) {
69 throw new InvalidArgumentException('The chunk length must be greater than zero.');
70 }
71
72 if ('' === $this->string) {
73 return [];
74 }
75
76 $rx = '/(';
77 while (65535 < $length) {
78 $rx .= '\X{65535}';
79 $length -= 65535;
80 }
81 $rx .= '\X{'.$length.'})/u';
82
83 $str = clone $this;
84 $chunks = [];
85
86 foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
87 $str->string = $chunk;
88 $chunks[] = clone $str;
89 }
90
91 return $chunks;
92 }
93
94 public function endsWith(string|iterable|AbstractString $suffix): bool
95 {
96 if ($suffix instanceof AbstractString) {
97 $suffix = $suffix->string;
98 } elseif (!\is_string($suffix)) {
99 return parent::endsWith($suffix);
100 }
101
102 $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
103 normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form);
104
105 if ('' === $suffix || false === $suffix) {
106 return false;
107 }
108
109 if ($this->ignoreCase) {
110 return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8');
111 }
112
113 return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix));
114 }
115
116 public function equalsTo(string|iterable|AbstractString $string): bool
117 {
118 if ($string instanceof AbstractString) {
119 $string = $string->string;
120 } elseif (!\is_string($string)) {
121 return parent::equalsTo($string);
122 }
123
124 $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
125 normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form);
126
127 if ('' !== $string && false !== $string && $this->ignoreCase) {
128 return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
129 }
130
131 return $string === $this->string;
132 }
133
134 public function indexOf(string|iterable|AbstractString $needle, int $offset = 0): ?int
135 {
136 if ($needle instanceof AbstractString) {
137 $needle = $needle->string;
138 } elseif (!\is_string($needle)) {
139 return parent::indexOf($needle, $offset);
140 }
141
142 $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
143 normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
144
145 if ('' === $needle || false === $needle) {
146 return null;
147 }
148
149 try {
150 $i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset);
151 } catch (\ValueError) {
152 return null;
153 }
154
155 return false === $i ? null : $i;
156 }
157
158 public function indexOfLast(string|iterable|AbstractString $needle, int $offset = 0): ?int
159 {
160 if ($needle instanceof AbstractString) {
161 $needle = $needle->string;
162 } elseif (!\is_string($needle)) {
163 return parent::indexOfLast($needle, $offset);
164 }
165
166 $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
167 normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
168
169 if ('' === $needle || false === $needle) {
170 return null;
171 }
172
173 $string = $this->string;
174
175 if (0 > $offset) {
176 // workaround https://bugs.php.net/74264
177 if (0 > $offset += grapheme_strlen($needle)) {
178 $string = grapheme_substr($string, 0, $offset);
179 }
180 $offset = 0;
181 }
182
183 $i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset);
184
185 return false === $i ? null : $i;
186 }
187
188 public function join(array $strings, ?string $lastGlue = null): static
189 {
190 $str = parent::join($strings, $lastGlue);
191 normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
192
193 return $str;
194 }
195
196 public function length(): int
197 {
198 return grapheme_strlen($this->string);
199 }
200
201 public function normalize(int $form = self::NFC): static
202 {
203 $str = clone $this;
204
205 if (\in_array($form, [self::NFC, self::NFKC], true)) {
206 normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
207 } elseif (!\in_array($form, [self::NFD, self::NFKD], true)) {
208 throw new InvalidArgumentException('Unsupported normalization form.');
209 } elseif (!normalizer_is_normalized($str->string, $form)) {
210 $str->string = normalizer_normalize($str->string, $form);
211 $str->ignoreCase = null;
212 }
213
214 return $str;
215 }
216
217 public function prepend(string ...$prefix): static
218 {
219 $str = clone $this;
220 $str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
221
222 if (normalizer_is_normalized($str->string)) {
223 return $str;
224 }
225
226 if (false === $string = normalizer_normalize($str->string)) {
227 throw new InvalidArgumentException('Invalid UTF-8 string.');
228 }
229
230 $str->string = $string;
231
232 return $str;
233 }
234
235 public function replace(string $from, string $to): static
236 {
237 $str = clone $this;
238 normalizer_is_normalized($from) ?: $from = normalizer_normalize($from);
239
240 if ('' !== $from && false !== $from) {
241 $tail = $str->string;
242 $result = '';
243 $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
244
245 while ('' !== $tail && false !== $i = $indexOf($tail, $from)) {
246 $slice = grapheme_substr($tail, 0, $i);
247 $result .= $slice.$to;
248 $tail = substr($tail, \strlen($slice) + \strlen($from));
249 }
250
251 $str->string = $result.$tail;
252
253 if (normalizer_is_normalized($str->string)) {
254 return $str;
255 }
256
257 if (false === $string = normalizer_normalize($str->string)) {
258 throw new InvalidArgumentException('Invalid UTF-8 string.');
259 }
260
261 $str->string = $string;
262 }
263
264 return $str;
265 }
266
267 public function replaceMatches(string $fromRegexp, string|callable $to): static
268 {
269 $str = parent::replaceMatches($fromRegexp, $to);
270 normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
271
272 return $str;
273 }
274
275 public function slice(int $start = 0, ?int $length = null): static
276 {
277 $str = clone $this;
278
279 $str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);
280
281 return $str;
282 }
283
284 public function splice(string $replacement, int $start = 0, ?int $length = null): static
285 {
286 $str = clone $this;
287
288 $start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0;
289 $length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length;
290 $str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647);
291
292 if (normalizer_is_normalized($str->string)) {
293 return $str;
294 }
295
296 if (false === $string = normalizer_normalize($str->string)) {
297 throw new InvalidArgumentException('Invalid UTF-8 string.');
298 }
299
300 $str->string = $string;
301
302 return $str;
303 }
304
305 public function split(string $delimiter, ?int $limit = null, ?int $flags = null): array
306 {
307 if (1 > $limit ??= 2147483647) {
308 throw new InvalidArgumentException('Split limit must be a positive integer.');
309 }
310
311 if ('' === $delimiter) {
312 throw new InvalidArgumentException('Split delimiter is empty.');
313 }
314
315 if (null !== $flags) {
316 return parent::split($delimiter.'u', $limit, $flags);
317 }
318
319 normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter);
320
321 if (false === $delimiter) {
322 throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
323 }
324
325 $str = clone $this;
326 $tail = $this->string;
327 $chunks = [];
328 $indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
329
330 while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) {
331 $str->string = grapheme_substr($tail, 0, $i);
332 $chunks[] = clone $str;
333 $tail = substr($tail, \strlen($str->string) + \strlen($delimiter));
334 --$limit;
335 }
336
337 $str->string = $tail;
338 $chunks[] = clone $str;
339
340 return $chunks;
341 }
342
343 public function startsWith(string|iterable|AbstractString $prefix): bool
344 {
345 if ($prefix instanceof AbstractString) {
346 $prefix = $prefix->string;
347 } elseif (!\is_string($prefix)) {
348 return parent::startsWith($prefix);
349 }
350
351 $form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
352 normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form);
353
354 if ('' === $prefix || false === $prefix) {
355 return false;
356 }
357
358 if ($this->ignoreCase) {
359 return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8');
360 }
361
362 return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES);
363 }
364
365 public function __wakeup(): void
366 {
367 if (!\is_string($this->string)) {
368 throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
369 }
370
371 normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
372 }
373
374 public function __clone()
375 {
376 if (null === $this->ignoreCase) {
377 normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
378 }
379
380 $this->ignoreCase = false;
381 }
382}
diff --git a/vendor/symfony/string/composer.json b/vendor/symfony/string/composer.json
new file mode 100644
index 0000000..10d0ee6
--- /dev/null
+++ b/vendor/symfony/string/composer.json
@@ -0,0 +1,44 @@
1{
2 "name": "symfony/string",
3 "type": "library",
4 "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
5 "keywords": ["string", "utf8", "utf-8", "grapheme", "i18n", "unicode"],
6 "homepage": "https://symfony.com",
7 "license": "MIT",
8 "authors": [
9 {
10 "name": "Nicolas Grekas",
11 "email": "p@tchwork.com"
12 },
13 {
14 "name": "Symfony Community",
15 "homepage": "https://symfony.com/contributors"
16 }
17 ],
18 "require": {
19 "php": ">=8.2",
20 "symfony/polyfill-ctype": "~1.8",
21 "symfony/polyfill-intl-grapheme": "~1.0",
22 "symfony/polyfill-intl-normalizer": "~1.0",
23 "symfony/polyfill-mbstring": "~1.0"
24 },
25 "require-dev": {
26 "symfony/error-handler": "^6.4|^7.0",
27 "symfony/emoji": "^7.1",
28 "symfony/http-client": "^6.4|^7.0",
29 "symfony/intl": "^6.4|^7.0",
30 "symfony/translation-contracts": "^2.5|^3.0",
31 "symfony/var-exporter": "^6.4|^7.0"
32 },
33 "conflict": {
34 "symfony/translation-contracts": "<2.5"
35 },
36 "autoload": {
37 "psr-4": { "Symfony\\Component\\String\\": "" },
38 "files": [ "Resources/functions.php" ],
39 "exclude-from-classmap": [
40 "/Tests/"
41 ]
42 },
43 "minimum-stability": "dev"
44}