1 //---------------------------------------------------------------------
2 // <copyright file="EnglishPluralizationService.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 using System.Collections.Generic;
13 using System.Globalization;
14 using System.Data.Entity.Design.Common;
15 using System.Text.RegularExpressions;
17 namespace System.Data.Entity.Design.PluralizationServices
19 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pluralization")]
20 internal class EnglishPluralizationService : PluralizationService, ICustomPluralizationMapping
22 private BidirectionalDictionary<string, string> _userDictionary;
23 private StringBidirectionalDictionary _irregularPluralsPluralizationService;
24 private StringBidirectionalDictionary _assimilatedClassicalInflectionPluralizationService;
25 private StringBidirectionalDictionary _oSuffixPluralizationService;
26 private StringBidirectionalDictionary _classicalInflectionPluralizationService;
27 private StringBidirectionalDictionary _irregularVerbPluralizationService;
28 private StringBidirectionalDictionary _wordsEndingWithSePluralizationService;
29 private StringBidirectionalDictionary _wordsEndingWithSisPluralizationService;
30 private StringBidirectionalDictionary _wordsEndingWithSusPluralizationService;
31 private StringBidirectionalDictionary _wordsEndingWithInxAnxYnxPluralizationService;
33 private List<string> _knownSingluarWords;
34 private List<string> _knownPluralWords;
36 private string[] _uninflectiveSuffixList =
37 new string[] { "fish", "ois", "sheep", "deer", "pos", "itis", "ism" };
39 private string[] _uninflectiveWordList =
41 "bison", "flounder", "pliers", "bream", "gallows", "proceedings",
42 "breeches", "graffiti", "rabies", "britches", "headquarters", "salmon",
43 "carp", "----", "scissors", "ch----is", "high-jinks", "sea-bass",
44 "clippers", "homework", "series", "cod", "innings", "shears", "contretemps",
45 "jackanapes", "species", "corps", "mackerel", "swine", "debris", "measles",
46 "trout", "diabetes", "mews", "tuna", "djinn", "mumps", "whiting", "eland",
47 "news", "wildebeest", "elk", "pincers", "police", "hair", "ice", "chaos",
48 "milk", "cotton", "pneumonoultramicroscopicsilicovolcanoconiosis",
49 "information", "aircraft", "scabies", "traffic", "corn", "millet", "rice",
50 "hay", "----", "tobacco", "cabbage", "okra", "broccoli", "asparagus",
51 "lettuce", "beef", "pork", "venison", "mutton", "cattle", "offspring",
52 "molasses", "shambles", "shingles"};
54 private Dictionary<string, string> _irregularVerbList =
55 new Dictionary<string, string>()
57 {"am", "are"}, {"are", "are"}, {"is", "are"}, {"was", "were"}, {"were", "were"},
58 {"has", "have"}, {"have", "have"}
61 private List<string> _pronounList =
64 "I", "we", "you", "he", "she", "they", "it",
65 "me", "us", "him", "her", "them",
66 "myself", "ourselves", "yourself", "himself", "herself", "itself",
67 "oneself", "oneselves",
68 "my", "our", "your", "his", "their", "its",
69 "mine", "yours", "hers", "theirs", "this", "that", "these", "those",
70 "all", "another", "any", "anybody", "anyone", "anything", "both", "each",
71 "other", "either", "everyone", "everybody", "everything", "most", "much", "nothing",
72 "nobody", "none", "one", "others", "some", "somebody", "someone", "something",
73 "what", "whatever", "which", "whichever", "who", "whoever", "whom", "whomever",
77 private Dictionary<string, string> _irregularPluralsDictionary =
78 new Dictionary<string, string>()
80 {"brother", "brothers"}, {"child", "children"},
81 {"cow", "cows"}, {"ephemeris", "ephemerides"}, {"genie", "genies"},
82 {"money", "moneys"}, {"mongoose", "mongooses"}, {"mythos", "mythoi"},
83 {"octopus", "octopuses"}, {"ox", "oxen"}, {"soliloquy", "soliloquies"},
84 {"trilby", "trilbys"}, {"crisis", "crises"}, {"synopsis","synopses"},
85 {"rose", "roses"}, {"gas","gases"}, {"bus", "buses"},
86 {"axis", "axes"},{"memo", "memos"}, {"casino","casinos"},
87 {"silo", "silos"},{"stereo", "stereos"}, {"studio","studios"},
88 {"lens", "lenses"}, {"alias","aliases"},
89 {"pie","pies"}, {"corpus","corpora"},
90 {"viscus", "viscera"},{"hippopotamus", "hippopotami"}, {"trace", "traces"},
91 {"person", "people"}, {"chili", "chilies"}, {"analysis", "analyses"},
92 {"basis", "bases"}, {"neurosis", "neuroses"}, {"oasis", "oases"},
93 {"synthesis", "syntheses"}, {"thesis", "theses"}, {"change", "changes"},
94 {"lie", "lies"}, {"calorie", "calories"}, {"freebie", "freebies"}, {"case", "cases"},
95 {"house", "houses"}, {"valve", "valves"}, {"cloth", "clothes"}, {"tie", "ties"},
96 {"movie", "movies"}, {"bonus", "bonuses"}, {"specimen", "specimens"}
99 Dictionary<string, string> _assimilatedClassicalInflectionDictionary =
100 new Dictionary<string, string>()
102 {"alumna", "alumnae"}, {"alga", "algae"}, {"vertebra", "vertebrae"},
103 {"codex", "codices"},
104 {"murex", "murices"}, {"silex", "silices"}, {"aphelion", "aphelia"},
105 {"hyperbaton", "hyperbata"}, {"perihelion", "perihelia"},
106 {"asyndeton", "asyndeta"}, {"noumenon", "noumena"},
107 {"phenomenon", "phenomena"}, {"criterion", "criteria"}, {"organon", "organa"},
108 {"prolegomenon", "prolegomena"}, {"agendum", "agenda"}, {"datum", "data"},
109 {"extremum", "extrema"}, {"bacterium", "bacteria"}, {"desideratum", "desiderata"},
110 {"stratum", "strata"}, {"candelabrum", "candelabra"}, {"erratum", "errata"},
111 {"ovum", "ova"}, {"forum", "fora"}, {"addendum", "addenda"}, {"stadium", "stadia"},
112 {"automaton", "automata"}, {"polyhedron", "polyhedra"},
115 Dictionary<string, string> _oSuffixDictionary =
116 new Dictionary<string, string>()
118 {"albino", "albinos"}, {"generalissimo", "generalissimos"},
119 {"manifesto", "manifestos"}, {"archipelago", "archipelagos"},
120 {"ghetto", "ghettos"}, {"medico", "medicos"}, {"armadillo", "armadillos"},
121 {"guano", "guanos"}, {"octavo", "octavos"}, {"commando", "commandos"},
122 {"inferno", "infernos"}, {"photo", "photos"}, {"ditto", "dittos"},
123 {"jumbo", "jumbos"}, {"pro", "pros"}, {"dynamo", "dynamos"},
124 {"lingo", "lingos"}, {"quarto", "quartos"}, {"embryo", "embryos"},
125 {"lumbago", "lumbagos"}, {"rhino", "rhinos"}, {"fiasco", "fiascos"},
126 {"magneto", "magnetos"}, {"stylo", "stylos"}
129 Dictionary<string, string> _classicalInflectionDictionary =
130 new Dictionary<string, string>()
132 {"stamen", "stamina"}, {"foramen", "foramina"}, {"lumen", "lumina"},
133 {"anathema", "anathemata"}, {"----", "----ta"}, {"oedema", "oedemata"},
134 {"bema", "bemata"}, {"enigma", "enigmata"}, {"sarcoma", "sarcomata"},
135 {"carcinoma", "carcinomata"}, {"gumma", "gummata"}, {"schema", "schemata"},
136 {"charisma", "charismata"}, {"lemma", "lemmata"}, {"soma", "somata"},
137 {"diploma", "diplomata"}, {"lymphoma", "lymphomata"}, {"stigma", "stigmata"},
138 {"dogma", "dogmata"}, {"magma", "magmata"}, {"stoma", "stomata"},
139 {"drama", "dramata"}, {"melisma", "melismata"}, {"trauma", "traumata"},
140 {"edema", "edemata"}, {"miasma", "miasmata"}, {"abscissa", "abscissae"},
141 {"formula", "formulae"}, {"medusa", "medusae"}, {"amoeba", "amoebae"},
142 {"hydra", "hydrae"}, {"nebula", "nebulae"}, {"antenna", "antennae"},
143 {"hyperbola", "hyperbolae"}, {"nova", "novae"}, {"aurora", "aurorae"},
144 {"lacuna", "lacunae"}, {"parabola", "parabolae"}, {"apex", "apices"},
145 {"latex", "latices"}, {"vertex", "vertices"}, {"cortex", "cortices"},
146 {"pontifex", "pontifices"}, {"vortex", "vortices"}, {"index", "indices"},
147 {"simplex", "simplices"}, {"iris", "irides"}, {"----oris", "----orides"},
148 {"alto", "alti"}, {"contralto", "contralti"}, {"soprano", "soprani"},
149 {"b----o", "b----i"}, {"crescendo", "crescendi"}, {"tempo", "tempi"},
150 {"canto", "canti"}, {"solo", "soli"}, {"aquarium", "aquaria"},
151 {"interregnum", "interregna"}, {"quantum", "quanta"},
152 {"compendium", "compendia"}, {"lustrum", "lustra"}, {"rostrum", "rostra"},
153 {"consortium", "consortia"}, {"maximum", "maxima"}, {"spectrum", "spectra"},
154 {"cranium", "crania"}, {"medium", "media"}, {"speculum", "specula"},
155 {"curriculum", "curricula"}, {"memorandum", "memoranda"}, {"stadium", "stadia"},
156 {"dictum", "dicta"}, {"millenium", "millenia"}, {"t----zium", "t----zia"},
157 {"emporium", "emporia"}, {"minimum", "minima"}, {"ultimatum", "ultimata"},
158 {"enconium", "enconia"}, {"momentum", "momenta"}, {"vacuum", "vacua"},
159 {"gymnasium", "gymnasia"}, {"optimum", "optima"}, {"velum", "vela"},
160 {"honorarium", "honoraria"}, {"phylum", "phyla"}, {"focus", "foci"},
161 {"nimbus", "nimbi"}, {"succubus", "succubi"}, {"fungus", "fungi"},
162 {"nucleolus", "nucleoli"}, {"torus", "tori"}, {"genius", "genii"},
163 {"radius", "radii"}, {"umbilicus", "umbilici"}, {"incubus", "incubi"},
164 {"stylus", "styli"}, {"uterus", "uteri"}, {"stimulus", "stimuli"}, {"apparatus", "apparatus"},
165 {"impetus", "impetus"}, {"prospectus", "prospectus"}, {"cantus", "cantus"},
166 {"nexus", "nexus"}, {"sinus", "sinus"}, {"coitus", "coitus"}, {"plexus", "plexus"},
167 {"status", "status"}, {"hiatus", "hiatus"}, {"afreet", "afreeti"},
168 {"afrit", "afriti"}, {"efreet", "efreeti"}, {"cherub", "cherubim"},
169 {"goy", "goyim"}, {"seraph", "seraphim"}, {"alumnus", "alumni"}
172 // this list contains all the plural words that being treated as singluar form, for example, "they" -> "they"
173 private List<string> _knownConflictingPluralList =
176 "they", "them", "their", "have", "were", "yourself", "are"
179 // this list contains the words ending with "se" and we special case these words since
180 // we need to add a rule for "ses" singularize to "s"
181 private Dictionary<string, string> _wordsEndingWithSeDictionary =
182 new Dictionary<string, string>()
184 {"house", "houses"}, {"case", "cases"}, {"enterprise", "enterprises"},
185 {"purchase", "purchases"}, {"surprise", "surprises"}, {"release", "releases"},
186 {"disease", "diseases"}, {"promise", "promises"}, {"refuse", "refuses"},
187 {"whose", "whoses"}, {"phase", "phases"}, {"noise", "noises"},
188 {"nurse", "nurses"}, {"rose", "roses"}, {"franchise", "franchises"},
189 {"supervise", "supervises"}, {"farmhouse", "farmhouses"},
190 {"suitcase", "suitcases"}, {"recourse", "recourses"}, {"impulse", "impulses"},
191 {"license", "licenses"}, {"diocese", "dioceses"}, {"excise", "excises"},
192 {"demise", "demises"}, {"blouse", "blouses"},
193 {"bruise", "bruises"}, {"misuse", "misuses"}, {"curse", "curses"},
194 {"prose", "proses"}, {"purse", "purses"}, {"goose", "gooses"},
195 {"tease", "teases"}, {"poise", "poises"}, {"vase", "vases"},
196 {"fuse", "fuses"}, {"muse", "muses"},
197 {"slaughterhouse", "slaughterhouses"}, {"clearinghouse", "clearinghouses"},
198 {"endonuclease", "endonucleases"}, {"steeplechase", "steeplechases"},
199 {"metamorphose", "metamorphoses"}, {"----", "----s"},
200 {"commonsense", "commonsenses"}, {"intersperse", "intersperses"},
201 {"merchandise", "merchandises"}, {"phosphatase", "phosphatases"},
202 {"summerhouse", "summerhouses"}, {"watercourse", "watercourses"},
203 {"catchphrase", "catchphrases"}, {"compromise", "compromises"},
204 {"greenhouse", "greenhouses"}, {"lighthouse", "lighthouses"},
205 {"paraphrase", "paraphrases"}, {"mayonnaise", "mayonnaises"},
206 {"----course", "----courses"}, {"apocalypse", "apocalypses"},
207 {"courthouse", "courthouses"}, {"powerhouse", "powerhouses"},
208 {"storehouse", "storehouses"}, {"glasshouse", "glasshouses"},
209 {"hypotenuse", "hypotenuses"}, {"peroxidase", "peroxidases"},
210 {"pillowcase", "pillowcases"}, {"roundhouse", "roundhouses"},
211 {"streetwise", "streetwises"}, {"expertise", "expertises"},
212 {"discourse", "discourses"}, {"warehouse", "warehouses"},
213 {"staircase", "staircases"}, {"workhouse", "workhouses"},
214 {"briefcase", "briefcases"}, {"clubhouse", "clubhouses"},
215 {"clockwise", "clockwises"}, {"concourse", "concourses"},
216 {"playhouse", "playhouses"}, {"turquoise", "turquoises"},
217 {"boathouse", "boathouses"}, {"cellulose", "celluloses"},
218 {"epitomise", "epitomises"}, {"gatehouse", "gatehouses"},
219 {"grandiose", "grandioses"}, {"menopause", "menopauses"},
220 {"penthouse", "penthouses"}, {"----horse", "----horses"},
221 {"transpose", "transposes"}, {"almshouse", "almshouses"},
222 {"customise", "customises"}, {"footloose", "footlooses"},
223 {"galvanise", "galvanises"}, {"princesse", "princesses"},
224 {"universe", "universes"}, {"workhorse", "workhorses"}
227 private Dictionary<string, string> _wordsEndingWithSisDictionary =
228 new Dictionary<string, string>()
230 {"analysis", "analyses"}, {"crisis", "crises"}, {"basis", "bases"},
231 {"atherosclerosis", "atheroscleroses"}, {"electrophoresis", "electrophoreses"},
232 {"psychoanalysis", "psychoanalyses"}, {"photosynthesis", "photosyntheses"},
233 {"amniocentesis", "amniocenteses"}, {"metamorphosis", "metamorphoses"},
234 {"toxoplasmosis", "toxoplasmoses"}, {"endometriosis", "endometrioses"},
235 {"tuberculosis", "tuberculoses"}, {"pathogenesis", "pathogeneses"},
236 {"osteoporosis", "osteoporoses"}, {"parenthesis", "parentheses"},
237 {"anastomosis", "anastomoses"}, {"peristalsis", "peristalses"},
238 {"hypothesis", "hypotheses"}, {"antithesis", "antitheses"},
239 {"apotheosis", "apotheoses"}, {"thrombosis", "thromboses"},
240 {"diagnosis", "diagnoses"}, {"synthesis", "syntheses"},
241 {"paralysis", "paralyses"}, {"prognosis", "prognoses"},
242 {"cirrhosis", "cirrhoses"}, {"sclerosis", "scleroses"},
243 {"psychosis", "psychoses"}, {"apoptosis", "apoptoses"}, {"symbiosis", "symbioses"}
246 private Dictionary<string, string> _wordsEndingWithSusDictionary =
247 new Dictionary<string, string>()
249 {"consensus","consensuses"}, {"census", "censuses"}
252 private Dictionary<string, string> _wordsEndingWithInxAnxYnxDictionary =
253 new Dictionary<string, string>()
255 {"sphinx", "sphinxes"},
256 {"larynx", "larynges"}, {"lynx", "lynxes"}, {"pharynx", "pharynxes"},
257 {"phalanx", "phalanxes"}
260 internal EnglishPluralizationService()
262 this.Culture = new CultureInfo("en");
264 this._userDictionary = new BidirectionalDictionary<string, string>();
266 this._irregularPluralsPluralizationService =
267 new StringBidirectionalDictionary(this._irregularPluralsDictionary);
268 this._assimilatedClassicalInflectionPluralizationService =
269 new StringBidirectionalDictionary(this._assimilatedClassicalInflectionDictionary);
270 this._oSuffixPluralizationService =
271 new StringBidirectionalDictionary(this._oSuffixDictionary);
272 this._classicalInflectionPluralizationService =
273 new StringBidirectionalDictionary(this._classicalInflectionDictionary);
274 this._wordsEndingWithSePluralizationService =
275 new StringBidirectionalDictionary(this._wordsEndingWithSeDictionary);
276 this._wordsEndingWithSisPluralizationService =
277 new StringBidirectionalDictionary(this._wordsEndingWithSisDictionary);
278 this._wordsEndingWithSusPluralizationService =
279 new StringBidirectionalDictionary(this._wordsEndingWithSusDictionary);
280 this._wordsEndingWithInxAnxYnxPluralizationService =
281 new StringBidirectionalDictionary(this._wordsEndingWithInxAnxYnxDictionary);
284 this._irregularVerbPluralizationService =
285 new StringBidirectionalDictionary(this._irregularVerbList);
287 this._knownSingluarWords = new List<string>(
288 _irregularPluralsDictionary.Keys
289 .Concat(_assimilatedClassicalInflectionDictionary.Keys)
290 .Concat(_oSuffixDictionary.Keys)
291 .Concat(_classicalInflectionDictionary.Keys)
292 .Concat(_irregularVerbList.Keys)
293 .Concat(_irregularPluralsDictionary.Keys)
294 .Concat(_wordsEndingWithSeDictionary.Keys)
295 .Concat(_wordsEndingWithSisDictionary.Keys)
296 .Concat(_wordsEndingWithSusDictionary.Keys)
297 .Concat(_wordsEndingWithInxAnxYnxDictionary.Keys)
298 .Concat(_uninflectiveWordList)
299 .Except(this._knownConflictingPluralList)); // see the _knowConflictingPluralList comment above
301 this._knownPluralWords = new List<string>(
302 _irregularPluralsDictionary.Values
303 .Concat(_assimilatedClassicalInflectionDictionary.Values)
304 .Concat(_oSuffixDictionary.Values)
305 .Concat(_classicalInflectionDictionary.Values)
306 .Concat(_irregularVerbList.Values)
307 .Concat(_irregularPluralsDictionary.Values)
308 .Concat(_wordsEndingWithSeDictionary.Values)
309 .Concat(_wordsEndingWithSisDictionary.Values)
310 .Concat(_wordsEndingWithSusDictionary.Values)
311 .Concat(_wordsEndingWithInxAnxYnxDictionary.Values)
312 .Concat(_uninflectiveWordList));
315 public override bool IsPlural(string word)
317 EDesignUtil.CheckArgumentNull<string>(word, "word");
319 if (this._userDictionary.ExistsInSecond(word))
323 if (this._userDictionary.ExistsInFirst(word))
328 if (this.IsUninflective(word) || this._knownPluralWords.Contains(word.ToLower(this.Culture)))
332 else if (this.Singularize(word).Equals(word))
342 public override bool IsSingular(string word)
344 EDesignUtil.CheckArgumentNull<string>(word, "word");
346 if (this._userDictionary.ExistsInFirst(word))
350 if (this._userDictionary.ExistsInSecond(word))
355 if (this.IsUninflective(word) || this._knownSingluarWords.Contains(word.ToLower(this.Culture)))
359 else if (!IsNoOpWord(word) && this.Singularize(word).Equals(word))
370 public override string Pluralize(string word)
372 EDesignUtil.CheckArgumentNull<string>(word, "word");
374 return Capitalize(word, InternalPluralize);
377 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
378 private string InternalPluralize(string word)
380 // words that we know of
381 if (this._userDictionary.ExistsInFirst(word))
383 return this._userDictionary.GetSecondValue(word);
386 if (IsNoOpWord(word))
392 string suffixWord = GetSuffixWord(word, out prefixWord);
395 if (IsNoOpWord(suffixWord))
397 return prefixWord + suffixWord;
400 // handle the word that do not inflect in the plural form
401 if (this.IsUninflective(suffixWord))
403 return prefixWord + suffixWord;
406 // if word is one of the known plural forms, then just return
407 if (this._knownPluralWords.Contains(suffixWord.ToLowerInvariant()) || this.IsPlural(suffixWord))
409 return prefixWord + suffixWord;
412 // handle irregular plurals, e.g. "ox" -> "oxen"
413 if (this._irregularPluralsPluralizationService.ExistsInFirst(suffixWord))
415 return prefixWord + this._irregularPluralsPluralizationService.GetSecondValue(suffixWord);
418 string newSuffixWord;
419 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
420 new List<string>() { "man" },
421 (s) => s.Remove(s.Length - 2, 2) + "en", this.Culture, out newSuffixWord))
423 return prefixWord + newSuffixWord;
426 // handle irregular inflections for common suffixes, e.g. "mouse" -> "mice"
427 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
428 new List<string>() { "louse", "mouse" },
429 (s) => s.Remove(s.Length - 4, 4) + "ice", this.Culture, out newSuffixWord))
431 return prefixWord + newSuffixWord;
434 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
435 new List<string>() { "tooth" },
436 (s) => s.Remove(s.Length - 4, 4) + "eeth", this.Culture, out newSuffixWord))
438 return prefixWord + newSuffixWord;
440 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
441 new List<string>() { "goose" },
442 (s) => s.Remove(s.Length - 4, 4) + "eese", this.Culture, out newSuffixWord))
444 return prefixWord + newSuffixWord;
446 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
447 new List<string>() { "foot" },
448 (s) => s.Remove(s.Length - 3, 3) + "eet", this.Culture, out newSuffixWord))
450 return prefixWord + newSuffixWord;
452 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
453 new List<string>() { "zoon" },
454 (s) => s.Remove(s.Length - 3, 3) + "oa", this.Culture, out newSuffixWord))
456 return prefixWord + newSuffixWord;
458 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
459 new List<string>() { "cis", "sis", "xis" },
460 (s) => s.Remove(s.Length - 2, 2) + "es", this.Culture, out newSuffixWord))
462 return prefixWord + newSuffixWord;
465 // handle assimilated classical inflections, e.g. vertebra -> vertebrae
466 if (this._assimilatedClassicalInflectionPluralizationService.ExistsInFirst(suffixWord))
468 return prefixWord + this._assimilatedClassicalInflectionPluralizationService.GetSecondValue(suffixWord);
471 // Handle the classical variants of modern inflections
473 if (this._classicalInflectionPluralizationService.ExistsInFirst(suffixWord))
475 return prefixWord + this._classicalInflectionPluralizationService.GetSecondValue(suffixWord);
478 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
479 new List<string>() { "trix" },
480 (s) => s.Remove(s.Length - 1, 1) + "ces", this.Culture, out newSuffixWord))
482 return prefixWord + newSuffixWord;
485 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
486 new List<string>() { "eau", "ieu" },
487 (s) => s + "x", this.Culture, out newSuffixWord))
489 return prefixWord + newSuffixWord;
492 if (this._wordsEndingWithInxAnxYnxPluralizationService.ExistsInFirst(suffixWord))
494 return prefixWord + this._wordsEndingWithInxAnxYnxPluralizationService.GetSecondValue(suffixWord);
497 // [cs]h and ss that take es as plural form
498 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord, new List<string>() { "ch", "sh", "ss" }, (s) => s + "es", this.Culture, out newSuffixWord))
500 return prefixWord + newSuffixWord;
503 // f, fe that take ves as plural form
504 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
505 new List<string>() { "alf", "elf", "olf", "eaf", "arf" },
506 (s) => s.EndsWith("deaf", true, this.Culture) ? s : s.Remove(s.Length - 1, 1) + "ves", this.Culture, out newSuffixWord))
508 return prefixWord + newSuffixWord;
511 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
512 new List<string>() { "nife", "life", "wife" },
513 (s) => s.Remove(s.Length - 2, 2) + "ves", this.Culture, out newSuffixWord))
515 return prefixWord + newSuffixWord;
518 // y takes ys as plural form if preceded by a vowel, but ies if preceded by a consonant, e.g. stays, skies
519 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
520 new List<string>() { "ay", "ey", "iy", "oy", "uy" },
521 (s) => s + "s", this.Culture, out newSuffixWord))
523 return prefixWord + newSuffixWord;
528 if (suffixWord.EndsWith("y", true, this.Culture))
530 return prefixWord + suffixWord.Remove(suffixWord.Length - 1, 1) + "ies";
533 // handle some of the words o -> os, and [vowel]o -> os, and the rest are o->oes
534 if (this._oSuffixPluralizationService.ExistsInFirst(suffixWord))
536 return prefixWord + this._oSuffixPluralizationService.GetSecondValue(suffixWord);
539 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
540 new List<string>() { "ao", "eo", "io", "oo", "uo" },
541 (s) => s + "s", this.Culture, out newSuffixWord))
543 return prefixWord + newSuffixWord;
546 if (suffixWord.EndsWith("o", true, this.Culture) || suffixWord.EndsWith("s", true, this.Culture))
548 return prefixWord + suffixWord + "es";
551 if (suffixWord.EndsWith("x", true, this.Culture))
553 return prefixWord + suffixWord + "es";
556 // cats, bags, hats, speakers
557 return prefixWord + suffixWord + "s";
560 public override string Singularize(string word)
562 EDesignUtil.CheckArgumentNull<string>(word, "word");
564 return Capitalize(word, InternalSingularize);
567 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
568 private string InternalSingularize(string word)
570 // words that we know of
571 if (this._userDictionary.ExistsInSecond(word))
573 return this._userDictionary.GetFirstValue(word);
576 if (IsNoOpWord(word))
582 string suffixWord = GetSuffixWord(word, out prefixWord);
584 if (IsNoOpWord(suffixWord))
586 return prefixWord + suffixWord;
589 // handle the word that is the same as the plural form
590 if (this.IsUninflective(suffixWord))
592 return prefixWord + suffixWord;
595 // if word is one of the known singular words, then just return
597 if (this._knownSingluarWords.Contains(suffixWord.ToLowerInvariant()))
599 return prefixWord + suffixWord;
602 // handle simple irregular verbs, e.g. was -> were
603 if (this._irregularVerbPluralizationService.ExistsInSecond(suffixWord))
605 return prefixWord + this._irregularVerbPluralizationService.GetFirstValue(suffixWord);
608 // handle irregular plurals, e.g. "ox" -> "oxen"
609 if (this._irregularPluralsPluralizationService.ExistsInSecond(suffixWord))
611 return prefixWord + this._irregularPluralsPluralizationService.GetFirstValue(suffixWord);
614 // handle singluarization for words ending with sis and pluralized to ses,
615 // e.g. "ses" -> "sis"
616 if (this._wordsEndingWithSisPluralizationService.ExistsInSecond(suffixWord))
618 return prefixWord + this._wordsEndingWithSisPluralizationService.GetFirstValue(suffixWord);
621 // handle words ending with se, e.g. "ses" -> "se"
622 if (this._wordsEndingWithSePluralizationService.ExistsInSecond(suffixWord))
624 return prefixWord + this._wordsEndingWithSePluralizationService.GetFirstValue(suffixWord);
627 // handle words ending with sus, e.g. "suses" -> "sus"
628 if (this._wordsEndingWithSusPluralizationService.ExistsInSecond(suffixWord))
630 return prefixWord + this._wordsEndingWithSusPluralizationService.GetFirstValue(suffixWord);
633 string newSuffixWord;
634 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
635 new List<string>() { "men" },
636 (s) => s.Remove(s.Length - 2, 2) + "an", this.Culture, out newSuffixWord))
638 return prefixWord + newSuffixWord;
641 // handle irregular inflections for common suffixes, e.g. "mouse" -> "mice"
642 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
643 new List<string>() { "lice", "mice" },
644 (s) => s.Remove(s.Length - 3, 3) + "ouse", this.Culture, out newSuffixWord))
646 return prefixWord + newSuffixWord;
649 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
650 new List<string>() { "teeth" },
651 (s) => s.Remove(s.Length - 4, 4) + "ooth", this.Culture, out newSuffixWord))
653 return prefixWord + newSuffixWord;
655 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
656 new List<string>() { "geese" },
657 (s) => s.Remove(s.Length - 4, 4) + "oose", this.Culture, out newSuffixWord))
659 return prefixWord + newSuffixWord;
661 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
662 new List<string>() { "feet" },
663 (s) => s.Remove(s.Length - 3, 3) + "oot", this.Culture, out newSuffixWord))
665 return prefixWord + newSuffixWord;
667 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
668 new List<string>() { "zoa" },
669 (s) => s.Remove(s.Length - 2, 2) + "oon", this.Culture, out newSuffixWord))
671 return prefixWord + newSuffixWord;
674 // [cs]h and ss that take es as plural form, this is being moved up since the sses will be override by the ses
675 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
676 new List<string>() { "ches", "shes", "sses" },
677 (s) => s.Remove(s.Length - 2, 2), this.Culture, out newSuffixWord))
679 return prefixWord + newSuffixWord;
683 // handle assimilated classical inflections, e.g. vertebra -> vertebrae
684 if (this._assimilatedClassicalInflectionPluralizationService.ExistsInSecond(suffixWord))
686 return prefixWord + this._assimilatedClassicalInflectionPluralizationService.GetFirstValue(suffixWord);
689 // Handle the classical variants of modern inflections
691 if (this._classicalInflectionPluralizationService.ExistsInSecond(suffixWord))
693 return prefixWord + this._classicalInflectionPluralizationService.GetFirstValue(suffixWord);
696 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
697 new List<string>() { "trices" },
698 (s) => s.Remove(s.Length - 3, 3) + "x", this.Culture, out newSuffixWord))
700 return prefixWord + newSuffixWord;
703 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
704 new List<string>() { "eaux", "ieux" },
705 (s) => s.Remove(s.Length - 1, 1), this.Culture, out newSuffixWord))
707 return prefixWord + newSuffixWord;
710 if (this._wordsEndingWithInxAnxYnxPluralizationService.ExistsInSecond(suffixWord))
712 return prefixWord + this._wordsEndingWithInxAnxYnxPluralizationService.GetFirstValue(suffixWord);
715 // f, fe that take ves as plural form
716 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
717 new List<string>() { "alves", "elves", "olves", "eaves", "arves" },
718 (s) => s.Remove(s.Length - 3, 3) + "f", this.Culture, out newSuffixWord))
720 return prefixWord + newSuffixWord;
723 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
724 new List<string>() { "nives", "lives", "wives" },
725 (s) => s.Remove(s.Length - 3, 3) + "fe", this.Culture, out newSuffixWord))
727 return prefixWord + newSuffixWord;
730 // y takes ys as plural form if preceded by a vowel, but ies if preceded by a consonant, e.g. stays, skies
731 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
732 new List<string>() { "ays", "eys", "iys", "oys", "uys" },
733 (s) => s.Remove(s.Length - 1, 1), this.Culture, out newSuffixWord))
735 return prefixWord + newSuffixWord;
740 if (suffixWord.EndsWith("ies", true, this.Culture))
742 return prefixWord + suffixWord.Remove(suffixWord.Length - 3, 3) + "y";
745 // handle some of the words o -> os, and [vowel]o -> os, and the rest are o->oes
746 if (this._oSuffixPluralizationService.ExistsInSecond(suffixWord))
748 return prefixWord + this._oSuffixPluralizationService.GetFirstValue(suffixWord);
751 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
752 new List<string>() { "aos", "eos", "ios", "oos", "uos" },
753 (s) => suffixWord.Remove(suffixWord.Length - 1, 1), this.Culture, out newSuffixWord))
755 return prefixWord + newSuffixWord;
763 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
764 new List<string>() { "ces" },
765 (s) => s.Remove(s.Length - 1, 1), this.Culture, out newSuffixWord))
767 return prefixWord + newSuffixWord;
770 if (PluralizationServiceUtil.TryInflectOnSuffixInWord(suffixWord,
771 new List<string>() { "ces", "ses", "xes" },
772 (s) => s.Remove(s.Length - 2, 2), this.Culture, out newSuffixWord))
774 return prefixWord + newSuffixWord;
777 if (suffixWord.EndsWith("oes", true, this.Culture))
779 return prefixWord + suffixWord.Remove(suffixWord.Length - 2, 2);
782 if (suffixWord.EndsWith("ss", true, this.Culture))
784 return prefixWord + suffixWord;
787 if (suffixWord.EndsWith("s", true, this.Culture))
789 return prefixWord + suffixWord.Remove(suffixWord.Length - 1, 1);
793 return prefixWord + suffixWord;
798 /// captalize the return word if the parameter is capitalized
799 /// if word is "Table", then return "Tables"
801 /// <param name="word"></param>
802 /// <param name="action"></param>
803 /// <returns></returns>
804 private string Capitalize(string word, Func<string, string> action)
806 string result = action(word);
808 if (IsCapitalized(word))
810 if (result.Length == 0)
813 StringBuilder sb = new StringBuilder(result.Length);
815 sb.Append(char.ToUpperInvariant(result[0]));
816 sb.Append(result.Substring(1));
817 return sb.ToString();
826 /// separate one combine word in to two parts, prefix word and the last word(suffix word)
828 /// <param name="word"></param>
829 /// <param name="prefixWord"></param>
830 /// <returns></returns>
831 private string GetSuffixWord(string word, out string prefixWord)
833 // use the last space to separate the words
834 int lastSpaceIndex = word.LastIndexOf(' ');
835 prefixWord = word.Substring(0, lastSpaceIndex + 1);
836 return word.Substring(lastSpaceIndex + 1);
841 private bool IsCapitalized(string word)
843 return string.IsNullOrEmpty(word) ? false : char.IsUpper(word, 0);
846 private bool IsAlphabets(string word)
848 // return false when the word is "[\s]*" or leading or tailing with spaces
849 // or contains non alphabetical characters
850 if (string.IsNullOrEmpty(word.Trim()) || !word.Equals(word.Trim()) ||
851 Regex.IsMatch(word, "[^a-zA-Z\\s]"))
861 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
862 private bool IsUninflective(string word)
864 EDesignUtil.CheckArgumentNull<string>(word, "word");
865 if (PluralizationServiceUtil.DoesWordContainSuffix(word, _uninflectiveSuffixList, this.Culture)
866 || (!word.ToLower(this.Culture).Equals(word) && word.EndsWith("ese", false, this.Culture))
867 || this._uninflectiveWordList.Contains(word.ToLowerInvariant()))
878 /// return true when the word is "[\s]*" or leading or tailing with spaces
879 /// or contains non alphabetical characters
881 /// <param name="word"></param>
882 /// <returns></returns>
883 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
884 private bool IsNoOpWord(string word)
887 if (!IsAlphabets(word) ||
889 _pronounList.Contains(word.ToLowerInvariant()))
901 #region ICustomPluralizationMapping Members
904 /// This method allow you to add word to internal PluralizationService of English.
905 /// If the singluar or the plural value was already added by this method, then an ArgumentException will be thrown.
907 /// <param name="singular"></param>
908 /// <param name="plural"></param>
909 public void AddWord(string singular, string plural)
911 EDesignUtil.CheckArgumentNull<string>(singular, "singular");
912 EDesignUtil.CheckArgumentNull<string>(plural, "plural");
914 if (this._userDictionary.ExistsInSecond(plural))
916 throw new ArgumentException(Strings.DuplicateEntryInUserDictionary("plural", plural), "plural");
918 else if (this._userDictionary.ExistsInFirst(singular))
920 throw new ArgumentException(Strings.DuplicateEntryInUserDictionary("singular", singular), "singular");
924 this._userDictionary.AddValue(singular, plural);