PluginProbe ʕ •ᴥ•ʔ
Kubio AI Page Builder / 2.8.6
Kubio AI Page Builder v2.8.6
2.9.0 2.8.6 2.8.5 2.8.4 2.8.3 2.8.2 2.8.1 trunk 1.0.0 1.0.1 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.9.0 2.0.0 2.1.1 2.1.2 2.1.3 2.2.0 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.5 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.6.1 2.6.2 2.6.3 2.6.5 2.6.6 2.6.7 2.7.0 2.7.1 2.7.2 2.7.3 2.8.0
kubio / vendor / symfony / string / Inflector / EnglishInflector.php
kubio / vendor / symfony / string / Inflector Last commit date
EnglishInflector.php 1 year ago FrenchInflector.php 1 year ago InflectorInterface.php 1 year ago
EnglishInflector.php
581 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\String\Inflector;
13
14 final 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 // article (articles), ancle (ancles)
125 ['sel', 3, true, true, 'le'],
126
127 // analyses (analysis), ellipses (ellipsis), fungi (fungus),
128 // neuroses (neurosis), theses (thesis), emphases (emphasis),
129 // oases (oasis), crises (crisis), houses (house), bases (base),
130 // atlases (atlas)
131 ['ses', 3, true, true, ['s', 'se', 'sis']],
132
133 // objectives (objective), alternative (alternatives)
134 ['sevit', 5, true, true, 'tive'],
135
136 // drives (drive)
137 ['sevird', 6, false, true, 'drive'],
138
139 // lives (life), wives (wife)
140 ['sevi', 4, false, true, 'ife'],
141
142 // moves (move)
143 ['sevom', 5, true, true, 'move'],
144
145 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
146 ['sev', 3, true, true, ['f', 've', 'ff']],
147
148 // axes (axis), axes (ax), axes (axe)
149 ['sexa', 4, false, false, ['ax', 'axe', 'axis']],
150
151 // indexes (index), matrixes (matrix)
152 ['sex', 3, true, false, 'x'],
153
154 // quizzes (quiz)
155 ['sezz', 4, true, false, 'z'],
156
157 // bureaus (bureau)
158 ['suae', 4, false, true, 'eau'],
159
160 // fees (fee), trees (tree), employees (employee)
161 ['see', 3, true, true, 'ee'],
162
163 // edges (edge)
164 ['segd', 4, true, true, 'dge'],
165
166 // roses (rose), garages (garage), cassettes (cassette),
167 // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
168 // shoes (shoe)
169 ['se', 2, true, true, ['', 'e']],
170
171 // status (status)
172 ['sutats', 6, true, true, 'status'],
173
174 // tags (tag)
175 ['s', 1, true, true, ''],
176
177 // chateaux (chateau)
178 ['xuae', 4, false, true, 'eau'],
179
180 // people (person)
181 ['elpoep', 6, true, true, 'person'],
182 ];
183
184 /**
185 * Map English singular to plural suffixes.
186 *
187 * @see http://english-zone.com/spelling/plurals.html
188 */
189 private const SINGULAR_MAP = [
190 // First entry: singular suffix, reversed
191 // Second entry: length of singular suffix
192 // Third entry: Whether the suffix may succeed a vowel
193 // Fourth entry: Whether the suffix may succeed a consonant
194 // Fifth entry: plural suffix, normal
195
196 // axes (axis)
197 ['sixa', 4, false, false, 'axes'],
198
199 // criterion (criteria)
200 ['airetirc', 8, false, false, 'criterion'],
201
202 // nebulae (nebula)
203 ['aluben', 6, false, false, 'nebulae'],
204
205 // children (child)
206 ['dlihc', 5, true, true, 'children'],
207
208 // prices (price)
209 ['eci', 3, false, true, 'ices'],
210
211 // services (service)
212 ['ecivres', 7, true, true, 'services'],
213
214 // lives (life), wives (wife)
215 ['efi', 3, false, true, 'ives'],
216
217 // selfies (selfie)
218 ['eifles', 6, true, true, 'selfies'],
219
220 // movies (movie)
221 ['eivom', 5, true, true, 'movies'],
222
223 // lice (louse)
224 ['esuol', 5, false, true, 'lice'],
225
226 // mice (mouse)
227 ['esuom', 5, false, true, 'mice'],
228
229 // geese (goose)
230 ['esoo', 4, false, true, 'eese'],
231
232 // houses (house), bases (base)
233 ['es', 2, true, true, 'ses'],
234
235 // geese (goose)
236 ['esoog', 5, true, true, 'geese'],
237
238 // caves (cave)
239 ['ev', 2, true, true, 'ves'],
240
241 // drives (drive)
242 ['evird', 5, false, true, 'drives'],
243
244 // objectives (objective), alternative (alternatives)
245 ['evit', 4, true, true, 'tives'],
246
247 // moves (move)
248 ['evom', 4, true, true, 'moves'],
249
250 // staves (staff)
251 ['ffats', 5, true, true, 'staves'],
252
253 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
254 ['ff', 2, true, true, 'ffs'],
255
256 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
257 ['f', 1, true, true, ['fs', 'ves']],
258
259 // arches (arch)
260 ['hc', 2, true, true, 'ches'],
261
262 // bushes (bush)
263 ['hs', 2, true, true, 'shes'],
264
265 // teeth (tooth)
266 ['htoot', 5, true, true, 'teeth'],
267
268 // albums (album)
269 ['mubla', 5, true, true, 'albums'],
270
271 // bacteria (bacterium), curricula (curriculum), media (medium), memoranda (memorandum), phenomena (phenomenon), strata (stratum)
272 ['mu', 2, true, true, 'a'],
273
274 // men (man), women (woman)
275 ['nam', 3, true, true, 'men'],
276
277 // people (person)
278 ['nosrep', 6, true, true, ['persons', 'people']],
279
280 // criteria (criterion)
281 ['noiretirc', 9, true, true, 'criteria'],
282
283 // phenomena (phenomenon)
284 ['nonemonehp', 10, true, true, 'phenomena'],
285
286 // echoes (echo)
287 ['ohce', 4, true, true, 'echoes'],
288
289 // heroes (hero)
290 ['oreh', 4, true, true, 'heroes'],
291
292 // atlases (atlas)
293 ['salta', 5, true, true, 'atlases'],
294
295 // aliases (alias)
296 ['saila', 5, true, true, 'aliases'],
297
298 // irises (iris)
299 ['siri', 4, true, true, 'irises'],
300
301 // analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
302 // theses (thesis), emphases (emphasis), oases (oasis),
303 // crises (crisis)
304 ['sis', 3, true, true, 'ses'],
305
306 // accesses (access), addresses (address), kisses (kiss)
307 ['ss', 2, true, false, 'sses'],
308
309 // syllabi (syllabus)
310 ['suballys', 8, true, true, 'syllabi'],
311
312 // buses (bus)
313 ['sub', 3, true, true, 'buses'],
314
315 // circuses (circus)
316 ['suc', 3, true, true, 'cuses'],
317
318 // hippocampi (hippocampus)
319 ['supmacoppih', 11, false, false, 'hippocampi'],
320
321 // campuses (campus)
322 ['sup', 3, true, true, 'puses'],
323
324 // status (status)
325 ['sutats', 6, true, true, ['status', 'statuses']],
326
327 // conspectuses (conspectus), prospectuses (prospectus)
328 ['sutcep', 6, true, true, 'pectuses'],
329
330 // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
331 ['su', 2, true, true, 'i'],
332
333 // news (news)
334 ['swen', 4, true, true, 'news'],
335
336 // feet (foot)
337 ['toof', 4, true, true, 'feet'],
338
339 // chateaux (chateau), bureaus (bureau)
340 ['uae', 3, false, true, ['eaus', 'eaux']],
341
342 // oxen (ox)
343 ['xo', 2, false, false, 'oxen'],
344
345 // hoaxes (hoax)
346 ['xaoh', 4, true, false, 'hoaxes'],
347
348 // indices (index)
349 ['xedni', 5, false, true, ['indicies', 'indexes']],
350
351 // fax (faxes, faxxes)
352 ['xaf', 3, true, true, ['faxes', 'faxxes']],
353
354 // boxes (box)
355 ['xo', 2, false, true, 'oxes'],
356
357 // indexes (index), matrixes (matrix), appendices (appendix)
358 ['x', 1, true, false, ['ces', 'xes']],
359
360 // babies (baby)
361 ['y', 1, false, true, 'ies'],
362
363 // quizzes (quiz)
364 ['ziuq', 4, true, false, 'quizzes'],
365
366 // waltzes (waltz)
367 ['z', 1, true, true, 'zes'],
368 ];
369
370 /**
371 * A list of words which should not be inflected, reversed.
372 */
373 private const UNINFLECTED = [
374 '',
375
376 // data
377 'atad',
378
379 // deer
380 'reed',
381
382 // equipment
383 'tnempiuqe',
384
385 // feedback
386 'kcabdeef',
387
388 // fish
389 'hsif',
390
391 // health
392 'htlaeh',
393
394 // history
395 'yrotsih',
396
397 // info
398 'ofni',
399
400 // information
401 'noitamrofni',
402
403 // money
404 'yenom',
405
406 // moose
407 'esoom',
408
409 // series
410 'seires',
411
412 // sheep
413 'peehs',
414
415 // species
416 'seiceps',
417
418 // traffic
419 'ciffart',
420
421 // aircraft
422 'tfarcria',
423
424 // hardware
425 'erawdrah',
426 ];
427
428 public function singularize(string $plural): array
429 {
430 $pluralRev = strrev($plural);
431 $lowerPluralRev = strtolower($pluralRev);
432 $pluralLength = \strlen($lowerPluralRev);
433
434 // Check if the word is one which is not inflected, return early if so
435 if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) {
436 return [$plural];
437 }
438
439 // The outer loop iterates over the entries of the plural table
440 // The inner loop $j iterates over the characters of the plural suffix
441 // in the plural table to compare them with the characters of the actual
442 // given plural suffix
443 foreach (self::PLURAL_MAP as $map) {
444 $suffix = $map[0];
445 $suffixLength = $map[1];
446 $j = 0;
447
448 // Compare characters in the plural table and of the suffix of the
449 // given plural one by one
450 while ($suffix[$j] === $lowerPluralRev[$j]) {
451 // Let $j point to the next character
452 ++$j;
453
454 // Successfully compared the last character
455 // Add an entry with the singular suffix to the singular array
456 if ($j === $suffixLength) {
457 // Is there any character preceding the suffix in the plural string?
458 if ($j < $pluralLength) {
459 $nextIsVowel = str_contains('aeiou', $lowerPluralRev[$j]);
460
461 if (!$map[2] && $nextIsVowel) {
462 // suffix may not succeed a vowel but next char is one
463 break;
464 }
465
466 if (!$map[3] && !$nextIsVowel) {
467 // suffix may not succeed a consonant but next char is one
468 break;
469 }
470 }
471
472 $newBase = substr($plural, 0, $pluralLength - $suffixLength);
473 $newSuffix = $map[4];
474
475 // Check whether the first character in the plural suffix
476 // is uppercased. If yes, uppercase the first character in
477 // the singular suffix too
478 $firstUpper = ctype_upper($pluralRev[$j - 1]);
479
480 if (\is_array($newSuffix)) {
481 $singulars = [];
482
483 foreach ($newSuffix as $newSuffixEntry) {
484 $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
485 }
486
487 return $singulars;
488 }
489
490 return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
491 }
492
493 // Suffix is longer than word
494 if ($j === $pluralLength) {
495 break;
496 }
497 }
498 }
499
500 // Assume that plural and singular is identical
501 return [$plural];
502 }
503
504 public function pluralize(string $singular): array
505 {
506 $singularRev = strrev($singular);
507 $lowerSingularRev = strtolower($singularRev);
508 $singularLength = \strlen($lowerSingularRev);
509
510 // Check if the word is one which is not inflected, return early if so
511 if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) {
512 return [$singular];
513 }
514
515 // The outer loop iterates over the entries of the singular table
516 // The inner loop $j iterates over the characters of the singular suffix
517 // in the singular table to compare them with the characters of the actual
518 // given singular suffix
519 foreach (self::SINGULAR_MAP as $map) {
520 $suffix = $map[0];
521 $suffixLength = $map[1];
522 $j = 0;
523
524 // Compare characters in the singular table and of the suffix of the
525 // given plural one by one
526
527 while ($suffix[$j] === $lowerSingularRev[$j]) {
528 // Let $j point to the next character
529 ++$j;
530
531 // Successfully compared the last character
532 // Add an entry with the plural suffix to the plural array
533 if ($j === $suffixLength) {
534 // Is there any character preceding the suffix in the plural string?
535 if ($j < $singularLength) {
536 $nextIsVowel = str_contains('aeiou', $lowerSingularRev[$j]);
537
538 if (!$map[2] && $nextIsVowel) {
539 // suffix may not succeed a vowel but next char is one
540 break;
541 }
542
543 if (!$map[3] && !$nextIsVowel) {
544 // suffix may not succeed a consonant but next char is one
545 break;
546 }
547 }
548
549 $newBase = substr($singular, 0, $singularLength - $suffixLength);
550 $newSuffix = $map[4];
551
552 // Check whether the first character in the singular suffix
553 // is uppercased. If yes, uppercase the first character in
554 // the singular suffix too
555 $firstUpper = ctype_upper($singularRev[$j - 1]);
556
557 if (\is_array($newSuffix)) {
558 $plurals = [];
559
560 foreach ($newSuffix as $newSuffixEntry) {
561 $plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
562 }
563
564 return $plurals;
565 }
566
567 return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
568 }
569
570 // Suffix is longer than word
571 if ($j === $singularLength) {
572 break;
573 }
574 }
575 }
576
577 // Assume that plural is singular with a trailing `s`
578 return [$singular.'s'];
579 }
580 }
581