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 dad5b01

Browse filesBrowse 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
Copy full SHA for dad5b01

File tree

2 files changed

+407
-0
lines changed
Filter options

2 files changed

+407
-0
lines changed

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

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