8000 feature #26890 [Inflector] Support pluralization in the inflector (mb… · symfony/symfony@dad5b01 · GitHub
[go: up one dir, main page]

Skip to content

Commit dad5b01

Browse files
committed
feature #26890 [Inflector] Support pluralization in the inflector (mbabker)
This PR was merged into the 4.3-dev branch. Discussion ---------- [Inflector] Support pluralization in the inflector | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | not yet | Fixed tickets | N/A | License | MIT | Doc PR | Not Yet At present the inflector only supports singularizing plural words, this PR adds the capability to pluralize singular words. Commits ------- 06920a7 Support pluralization in the inflector
2 parents 592e72f + 06920a7 commit dad5b01

File tree

2 files changed

+407
-0
lines changed

2 files changed

+407
-0
lines changed

src/Symfony/Component/Inflector/Inflector.php

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,179 @@ final class Inflector
139139
['elpoep', 6, true, true, 'person'],
140140
];
141141

142+
/**
143+
* Map English singular to plural suffixes.
144+
*
145+
* @var array
146+
*
147+
* @see http://english-zone.com/spelling/plurals.html
148+
*/
149+
private static $singularMap = array(
150+
// First entry: singular suffix, reversed
151+
// Second entry: length of singular suffix
152+
// Third entry: Whether the suffix may succeed a vocal
153+
// Fourth entry: Whether the suffix may succeed a consonant
154+
// Fifth entry: plural suffix, normal
155+
156+
// criterion (criteria)
157+
array('airetirc', 8, false, false, 'criterion'),
158+
159+
// nebulae (nebula)
160+
array('aluben', 6, false, false, 'nebulae'),
161+
162+
// children (child)
163+
array('dlihc', 5, true, true, 'children'),
164+
165+
// prices (price)
166+
array('eci', 3, false, true, 'ices'),
167+
168+
// services (service)
169+
array('ecivres', 7, true, true, 'services'),
170+
171+
// lives (life), wives (wife)
172+
array('efi', 3, false, true, 'ives'),
173+
174+
// selfies (selfie)
175+
array('eifles', 6, true, true, 'selfies'),
176+
177+
// movies (movie)
178+
array('eivom', 5, true, true, 'movies'),
179+
180+
// lice (louse)
181+
array('esuol', 5, false, true, 'lice'),
182+
183+
// mice (mouse)
184+
array('esuom', 5, false, true, 'mice'),
185+
186+
// geese (goose)
187+
array('esoo', 4, false, true, 'eese'),
188+
189+
// houses (house), bases (base)
190+
array('es', 2, true, true, 'ses'),
191+
192+
// geese (goose)
193+
array('esoog', 5, true, true, 'geese'),
194+
195+
// caves (cave)
196+
array('ev', 2, true, true, 'ves'),
197+
198+
// drives (drive)
199+
array('evird', 5, false, true, 'drives'),
200+
201+
// objectives (objective), alternative (alternatives)
202+
array('evit', 4, true, true, 'tives'),
203+
204+
// moves (move)
205+
array('evom', 4, true, true, 'moves'),
206+
207+
// staves (staff)
208+
array('ffats', 5, true, true, 'staves'),
209+
210+
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
211+
array('ff', 2, true, true, 'ffs'),
212+
213+
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
214+
array('f', 1, true, true, array('fs', 'ves')),
215+
216+
// arches (arch)
217+
array('hc', 2, true, true, 'ches'),
218+
219+
// bushes (bush)
220+
array('hs', 2, true, true, 'shes'),
221+
222+
// teeth (tooth)
223+
array('htoot', 5, true, true, 'teeth'),
224+
225+
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
226+
array('mu', 2, true, true, 'a'),
227+
228+
// echoes (echo)
229+
array('ohce', 4, true, true, 'echoes'),
230+
231+
// men (man), women (woman)
232+
array('nam', 3, true, true, 'men'),
233+
234+
// people (person)
235+
array('nosrep', 6, true, true, array('persons', 'people')),
236+
237+
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
238+
array('noi', 3, true, true, 'ions'),
239+
240+
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
241+
array('no', 2, true, true, 'a'),
242+
243+
// atlases (atlas)
244+
array('salta', 5, true, true, 'atlases'),
245+
246+
// irises (iris)
247+
array('siri', 4, true, true, 'irises'),
248+
249+
// analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
250+
// theses (thesis), emphases (emphasis), oases (oasis),
251+
// crises (crisis)
252+
array('sis', 3, true, true, 'ses'),
253+
254+
// accesses (access), addresses (address), kisses (kiss)
255+
array('ss', 2, true, false, 'sses'),
256+
257+
// syllabi (syllabus)
258+
array('suballys', 8, true, true, 'syllabi'),
259+
260+
// buses (bus)
261+
array('sub', 3, true, true, 'buses'),
262+
263+
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
264+
array('su', 2, true, true, 'i'),
265+
266+
// news (news)
267+
array('swen', 4, true, true, 'news'),
268+
269+
// feet (foot)
270+
array('toof', 4, true, true, 'feet'),
271+
272+
// chateaux (chateau), bureaus (bureau)
273+
array('uae', 3, false, true, array('eaus', 'eaux')),
274+
275+
// oxen (ox)
276+
array('xo', 2, false, false, 'oxen'),
277+
278+
// hoaxes (hoax)
279+
array('xaoh', 4, true, false, 'hoaxes'),
280+
281+
// indices (index)
282+
array('xedni', 5, false, true, array('indicies', 'indexes')),
283+
284+
// indexes (index), matrixes (matrix)
285+
array('x', 1, true, false, array('cies', 'xes')),
286+
287+
// appendices (appendix)
288+
array('xi', 2, false, true, 'ices'),
289+
290+
// babies (baby)
291+
array('y', 1, false, true, 'ies'),
292+
293+
// quizzes (quiz)
294+
array('ziuq', 4, true, false, 'quizzes'),
295+
296+
// waltzes (waltz)
297+
array('z', 1, true, false, 'zes'),
298+
);
299+
300+
/**
301+
* A list of words which should not be inflected
302+
*
303+
* @var array
304+
*/
305+
private static $uninflected = array(
306+
'data',
307+
'deer',
308+
'feedback',
309+
'fish',
310+
'moose',
311+
'series',
312+
'sheep',
313+
);
314+
142315
/**
143316
* This class should not be instantiated.
144317
*/
@@ -165,6 +338,11 @@ public static function singularize(string $plural)
165338
$lowerPluralRev = strtolower($pluralRev);
166339
$pluralLength = \strlen($lowerPluralRev);
167340

341+
// Check if the word is one which is not inflected, return early if so
342+
if (in_array(strtolower($plural), self::$uninflected, true)) {
343+
return $plural;
344+
}
345+
168346
// The outer loop iterates over the entries of the plural table
169347
// The inner loop $j iterates over the characters of the plural suffix
170348
// in the plural table to compare them with the characters of the actual
@@ -229,4 +407,94 @@ public static function singularize(string $plural)
229407
// Assume that plural and singular is identical
230408
return $plural;
231409
}
410+
411+
/**
412+
* Returns the plural form of a word.
413+
*
414+
* If the method can't determine the form with certainty, an array of the
415+
* possible plurals is returned.
416+
*
417+
* @param string $singular A word in plural form
418+
*
419+
* @return string|array The plural form or an array of possible plural
420+
* forms
421+
*
422+
* @internal
423+
*/
424+
public static function pluralize(string $singular)
425+
{
426+
$singularRev = strrev($singular);
427+
$lowerSingularRev = strtolower($singularRev);
428+
$singularLength = strlen($lowerSingularRev);
429+
430+
// Check if the word is one which is not inflected, return early if so
431+
if (in_array(strtolower($singular), self::$uninflected, true)) {
432+
return $singular;
433+
}
434+
435+
// The outer loop iterates over the entries of the singular table
436+
// The inner loop $j iterates over the characters of the singular suffix
437+
// in the singular table to compare them with the characters of the actual
438+
// given singular suffix
439+
foreach (self::$singularMap as $map) {
440+
$suffix = $map[0];
441+
$suffixLength = $map[1];
442+
$j = 0;
443+
444+
// Compare characters in the singular table and of the suffix of the
445+
// given plural one by one
446+
447+
while ($suffix[$j] === $lowerSingularRev[$j]) {
448+
// Let $j point to the next character
449+
++$j;
450+
451+
// Successfully compared the last character
452+
// Add an entry with the plural suffix to the plural array
453+
if ($j === $suffixLength) {
454+
// Is there any character preceding the suffix in the plural string?
455+
if ($j < $singularLength) {
456+
$nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
457+
458+
if (!$map[2] && $nextIsVocal) {
459+
// suffix may not succeed a vocal but next char is one
460+
break;
461+
}
462+
463+
if (!$map[3] && !$nextIsVocal) {
464+
// suffix may not succeed a consonant but next char is one
465+
break;
466+
}
467+
}
468+
469+
$newBase = substr($singular, 0, $singularLength - $suffixLength);
470+
$newSuffix = $map[4];
471+
472+
// Check whether the first character in the singular suffix
473+
// is uppercased. If yes, uppercase the first character in
474+
// the singular suffix too
475+
$firstUpper = ctype_upper($singularRev[$j - 1]);
476+
477+
if (is_array($newSuffix)) {
478+
$plurals = array();
479+
480+
foreach ($newSuffix as $newSuffixEntry) {
481+
$plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
482+
}
483+
484+
return $plurals;
485+
}
486+
487+
return $newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix);
488+
}
489+
490+
// Suffix is longer than word
491+
if ($j === $singularLength) {
492+
break;
493+
}
494+
}
495+
}
496+
497+
// Assume that plural is singular with a trailing `s`
498+
return $singular.'s';
499+
}
232500
}

0 commit comments

Comments
 (0)
0