Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 06920a7

Browse filesBrowse files
committed
Support pluralization in the inflector
1 parent 9a99955 commit 06920a7
Copy full SHA for 06920a7

File tree

2 files changed

+407
-3
lines changed
Filter options

2 files changed

+407
-3
lines changed

‎src/Symfony/Component/Inflector/Inflector.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Inflector/Inflector.php
+268-3Lines 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, 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.