summaryrefslogtreecommitdiff
path: root/vendor/symfony/string/Slugger/AsciiSlugger.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/symfony/string/Slugger/AsciiSlugger.php')
-rw-r--r--vendor/symfony/string/Slugger/AsciiSlugger.php207
1 files changed, 207 insertions, 0 deletions
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}