4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\PropertyAccess
;
15 * Creates singulars from plurals.
17 * @author Bernhard Schussek <bschussek@gmail.com>
22 * Map english plural to singular suffixes
26 * @see http://english-zone.com/spelling/plurals.html
27 * @see http://www.scribd.com/doc/3271143/List-of-100-Irregular-Plural-Nouns-in-English
29 private static $pluralMap = array(
30 // First entry: plural suffix, reversed
31 // Second entry: length of plural suffix
32 // Third entry: Whether the suffix may succeed a vocal
33 // Fourth entry: Whether the suffix may succeed a consonant
34 // Fifth entry: singular suffix, normal
36 // bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
37 array('a', 1, true, true, array('on', 'um')),
40 array('ea', 2, true, true, 'a'),
42 // mice (mouse), lice (louse)
43 array('eci', 3, false, true, 'ouse'),
46 array('esee', 4, false, true, 'oose'),
48 // fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
49 array('i', 1, true, true, 'us'),
51 // men (man), women (woman)
52 array('nem', 3, true, true, 'man'),
55 array('nerdlihc', 8, true, true, 'child'),
58 array('nexo', 4, false, false, 'ox'),
60 // indices (index), appendices (appendix), prices (price)
61 array('seci', 4, false, true, array('ex', 'ix', 'ice')),
64 array('sei', 3, false, true, 'y'),
66 // analyses (analysis), ellipses (ellipsis), funguses (fungus),
67 // neuroses (neurosis), theses (thesis), emphases (emphasis),
68 // oases (oasis), crises (crisis), houses (house), bases (base),
69 // atlases (atlas), kisses (kiss)
70 array('ses', 3, true, true, array('s', 'se', 'sis')),
72 // objectives (objective), alternative (alternatives)
73 array('sevit', 5, true, true, 'tive'),
75 // lives (life), wives (wife)
76 array('sevi', 4, false, true, 'ife'),
78 // hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
79 array('sev', 3, true, true, 'f'),
81 // axes (axis), axes (ax), axes (axe)
82 array('sexa', 4, false, false, array('ax', 'axe', 'axis')),
84 // indexes (index), matrixes (matrix)
85 array('sex', 3, true, false, 'x'),
88 array('sezz', 4, true, false, 'z'),
91 array('suae', 4, false, true, 'eau'),
93 // roses (rose), garages (garage), cassettes (cassette),
94 // waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
96 array('se', 2, true, true, array('', 'e')),
99 array('s', 1, true, true, ''),
101 // chateaux (chateau)
102 array('xuae', 4, false, true, 'eau'),
106 * This class should not be instantiated
108 private function __construct() {}
111 * Returns the singular form of a word
113 * If the method can't determine the form with certainty, an array of the
114 * possible singulars is returned.
116 * @param string $plural A word in plural form
117 * @return string|array The singular form or an array of possible singular
120 public static function singularify($plural)
122 $pluralRev = strrev($plural);
123 $lowerPluralRev = strtolower($pluralRev);
124 $pluralLength = strlen($lowerPluralRev);
126 // The outer loop iterates over the entries of the plural table
127 // The inner loop $j iterates over the characters of the plural suffix
128 // in the plural table to compare them with the characters of the actual
129 // given plural suffix
130 foreach (self
::$pluralMap as $map) {
132 $suffixLength = $map[1];
135 // Compare characters in the plural table and of the suffix of the
136 // given plural one by one
137 while ($suffix[$j] === $lowerPluralRev[$j]) {
138 // Let $j point to the next character
141 // Successfully compared the last character
142 // Add an entry with the singular suffix to the singular array
143 if ($j === $suffixLength) {
144 // Is there any character preceding the suffix in the plural string?
145 if ($j < $pluralLength) {
146 $nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
148 if (!$map[2] && $nextIsVocal) {
149 // suffix may not succeed a vocal but next char is one
153 if (!$map[3] && !$nextIsVocal) {
154 // suffix may not succeed a consonant but next char is one
159 $newBase = substr($plural, 0, $pluralLength - $suffixLength);
160 $newSuffix = $map[4];
162 // Check whether the first character in the plural suffix
163 // is uppercased. If yes, uppercase the first character in
164 // the singular suffix too
165 $firstUpper = ctype_upper($pluralRev[$j - 1]);
167 if (is_array($newSuffix)) {
168 $singulars = array();
170 foreach ($newSuffix as $newSuffixEntry) {
171 $singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
177 return $newBase.($firstUpper ? ucFirst($newSuffix) : $newSuffix);
180 // Suffix is longer than word
181 if ($j === $pluralLength) {
187 // Convert teeth to tooth, feet to foot
188 if (false !== ($pos = strpos($plural, 'ee'))) {
189 return substr_replace($plural, 'oo', $pos, 2);
192 // Assume that plural and singular is identical