8000 Support pluralization in the inflector · symfony/symfony@06920a7 · GitHub
[go: up one dir, main page]

Skip to content

Commit 06920a7

Browse files
committed
Support pluralization in the inflector
1 parent 9a99955 commit 06920a7

File tree

2 files changed

+407
-3
lines changed

2 files changed

+407
-3
lines changed

src/Symfony/Component/Inflector/Inflector.php

Lines changed: 268 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,6 @@ final class Inflector
8282
// news (news)
8383
array('swen', 4, true, true, 'news'),
8484

85-
// series (series)
86-
array('seires', 6, true, true, 'series'),
87-
8885
// babies (baby)
8986
array('sei', 3, false, true, 'y'),
9087

@@ -139,6 +136,179 @@ final class Inflector
139136
array('elpoep', 6, true, true, 'person'),
140137
);
141138

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

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

0 commit comments

Comments
 (0)
0