-
Notifications
You must be signed in to change notification settings - Fork 966
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Break out Vocabulary/DefaultVocabulary to make Inflector more extensible. #408
Closed
jkodroff
wants to merge
4
commits into
Humanizr:master
from
jkodroff:402-exclude-terms-from-pluralization-and-singularization
+322
−188
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> | ||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/FORCE_IFELSE_BRACES_STYLE/@EntryValue">ONLY_FOR_MULTILINE</s:String> | ||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String> | ||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> | ||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean> | ||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean> | ||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
namespace Humanizer.Inflections | ||
{ | ||
/// <summary> | ||
/// Container for registered Vocabularies. At present, only a single vocabulary is supported: Default. | ||
/// </summary> | ||
public class Vocabularies | ||
{ | ||
private static Vocabulary _default; | ||
|
||
/// <summary> | ||
/// The default vocabulary used for singular/plural irregularities. | ||
/// Rules can be added to this vocabulary and will be picked up by called to Singularize() and Pluralize(). | ||
/// At this time, multiple vocabularies and removing existing rules are not supported. | ||
/// </summary> | ||
public static Vocabulary Default | ||
{ | ||
get | ||
{ | ||
if (_default == null) | ||
BuildDefault(); | ||
|
||
return _default; | ||
} | ||
} | ||
|
||
private static void BuildDefault() | ||
{ | ||
_default = new Vocabulary(); | ||
|
||
_default.AddPlural("$", "s"); | ||
_default.AddPlural("s$", "s"); | ||
_default.AddPlural("(ax|test)is$", "$1es"); | ||
_default.AddPlural("(octop|vir|alumn|fung)us$", "$1i"); | ||
_default.AddPlural("(alias|status)$", "$1es"); | ||
_default.AddPlural("(bu)s$", "$1ses"); | ||
_default.AddPlural("(buffal|tomat|volcan)o$", "$1oes"); | ||
_default.AddPlural("([ti])um$", "$1a"); | ||
_default.AddPlural("sis$", "ses"); | ||
_default.AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves"); | ||
_default.AddPlural("(hive)$", "$1s"); | ||
_default.AddPlural("([^aeiouy]|qu)y$", "$1ies"); | ||
_default.AddPlural("(x|ch|ss|sh)$", "$1es"); | ||
_default.AddPlural("(matr|vert|ind)ix|ex$", "$1ices"); | ||
_default.AddPlural("([m|l])ouse$", "$1ice"); | ||
_default.AddPlural("^(ox)$", "$1en"); | ||
_default.AddPlural("(quiz)$", "$1zes"); | ||
_default.AddPlural("(campus)$", "$1es"); | ||
_default.AddPlural("^is$", "are"); | ||
|
||
_default.AddSingular("s$", ""); | ||
_default.AddSingular("(n)ews$", "$1ews"); | ||
_default.AddSingular("([ti])a$", "$1um"); | ||
_default.AddSingular("((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$", "$1$2sis"); | ||
_default.AddSingular("(^analy)ses$", "$1sis"); | ||
_default.AddSingular("([^f])ves$", "$1fe"); | ||
_default.AddSingular("(hive)s$", "$1"); | ||
_default.AddSingular("(tive)s$", "$1"); | ||
_default.AddSingular("([lr])ves$", "$1f"); | ||
_default.AddSingular("([^aeiouy]|qu)ies$", "$1y"); | ||
_default.AddSingular("(s)eries$", "$1eries"); | ||
_default.AddSingular("(m)ovies$", "$1ovie"); | ||
_default.AddSingular("(x|ch|ss|sh)es$", "$1"); | ||
_default.AddSingular("([m|l])ice$", "$1ouse"); | ||
_default.AddSingular("(bus)es$", "$1"); | ||
_default.AddSingular("(o)es$", "$1"); | ||
_default.AddSingular("(shoe)s$", "$1"); | ||
_default.AddSingular("(cris|ax|test)es$", "$1is"); | ||
_default.AddSingular("(octop|vir|alumn|fung)i$", "$1us"); | ||
_default.AddSingular("(alias|status)es$", "$1"); | ||
_default.AddSingular("^(ox)en", "$1"); | ||
_default.AddSingular("(vert|ind)ices$", "$1ex"); | ||
_default.AddSingular("(matr)ices$", "$1ix"); | ||
_default.AddSingular("(quiz)zes$", "$1"); | ||
_default.AddSingular("(campus)es$", "$1"); | ||
_default.AddSingular("^are$", "is"); | ||
|
||
_default.AddIrregular("person", "people"); | ||
_default.AddIrregular("man", "men"); | ||
_default.AddIrregular("child", "children"); | ||
_default.AddIrregular("sex", "sexes"); | ||
_default.AddIrregular("move", "moves"); | ||
_default.AddIrregular("goose", "geese"); | ||
_default.AddIrregular("alumna", "alumnae"); | ||
_default.AddIrregular("criterion", "criteria"); | ||
_default.AddIrregular("wave", "waves"); | ||
|
||
_default.AddUncountable("equipment"); | ||
_default.AddUncountable("information"); | ||
_default.AddUncountable("rice"); | ||
_default.AddUncountable("money"); | ||
_default.AddUncountable("species"); | ||
_default.AddUncountable("series"); | ||
_default.AddUncountable("fish"); | ||
_default.AddUncountable("sheep"); | ||
_default.AddUncountable("deer"); | ||
_default.AddUncountable("aircraft"); | ||
_default.AddUncountable("oz"); | ||
_default.AddUncountable("tsp"); | ||
_default.AddUncountable("tbsp"); | ||
_default.AddUncountable("ml"); | ||
_default.AddUncountable("l"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
using System.Collections.Generic; | ||
using System.Text.RegularExpressions; | ||
|
||
namespace Humanizer.Inflections | ||
{ | ||
/// <summary> | ||
/// A container for exceptions to simple pluralization/singularization rules. | ||
/// Vocabularies.Default contains an extensive list of rules for US English. | ||
/// At this time, multiple vocabularies and removing existing rules are not supported. | ||
/// </summary> | ||
public class Vocabulary | ||
{ | ||
private readonly List<Rule> _plurals = new List<Rule>(); | ||
private readonly List<Rule> _singulars = new List<Rule>(); | ||
private readonly List<string> _uncountables = new List<string>(); | ||
|
||
/// <summary> | ||
/// Adds a word to the vocabulary which cannot easily be pluralized/singularized by RegEx, e.g. "person" and "people". | ||
/// </summary> | ||
/// <param name="singular">The singular form of the irregular word, e.g. "person".</param> | ||
/// <param name="plural">The plural form of the irregular word, e.g. "people".</param> | ||
public void AddIrregular(string singular, string plural) | ||
{ | ||
AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1)); | ||
AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1)); | ||
} | ||
|
||
/// <summary> | ||
/// Adds an uncountable word to the vocabulary, e.g. "fish". Will be ignored when plurality is changed. | ||
/// </summary> | ||
/// <param name="word">Word to be added to the list of uncountables.</param> | ||
public void AddUncountable(string word) | ||
{ | ||
_uncountables.Add(word.ToLower()); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a rule to the vocabulary that does not follow trivial rules for pluralization, e.g. "bus" -> "buses" | ||
/// </summary> | ||
/// <param name="rule">RegEx to be matched, case insensitive, e.g. "(bus)es$"</param> | ||
/// <param name="replacement">RegEx replacement e.g. "$1"</param> | ||
public void AddPlural(string rule, string replacement) | ||
{ | ||
_plurals.Add(new Rule(rule, replacement)); | ||
} | ||
|
||
/// <summary> | ||
/// Adds a rule to the vocabulary that does not follow trivial rules for singularization, e.g. "vertices/indices -> "vertex/index" | ||
/// </summary> | ||
/// <param name="rule">RegEx to be matched, case insensitive, e.g. ""(vert|ind)ices$""</param> | ||
/// <param name="replacement">RegEx replacement e.g. "$1ex"</param> | ||
public void AddSingular(string rule, string replacement) | ||
{ | ||
_singulars.Add(new Rule(rule, replacement)); | ||
} | ||
|
||
/// <summary> | ||
/// Pluralizes the provided input considering irregular words | ||
/// </summary> | ||
/// <param name="word">Word to be pluralized</param> | ||
/// <param name="inputIsKnownToBeSingular">Normally you call Pluralize on singular words; but if you're unsure call it with false</param> | ||
/// <returns></returns> | ||
public string Pluralize(string word, bool inputIsKnownToBeSingular = true) | ||
{ | ||
var result = ApplyRules(_plurals, word); | ||
|
||
if (inputIsKnownToBeSingular) | ||
return result; | ||
|
||
var asSingular = ApplyRules(_singulars, word); | ||
var asSingularAsPlural = ApplyRules(_plurals, asSingular); | ||
if (asSingular != null && asSingular != word && asSingular + "s" != word && asSingularAsPlural == word && result != word) | ||
return word; | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Singularizes the provided input considering irregular words | ||
/// </summary> | ||
/// <param name="word">Word to be singularized</param> | ||
/// <param name="inputIsKnownToBePlural">Normally you call Singularize on plural words; but if you're unsure call it with false</param> | ||
/// <returns></returns> | ||
public string Singularize(string word, bool inputIsKnownToBePlural = true) | ||
{ | ||
|
||
var result = ApplyRules(_singulars, word); | ||
|
||
if (inputIsKnownToBePlural) | ||
return result; | ||
|
||
// the Plurality is unknown so we should check all possibilities | ||
var asPlural = ApplyRules(_plurals, word); | ||
var asPluralAsSingular = ApplyRules(_singulars, asPlural); | ||
if (asPlural != word && word + "s" != asPlural && asPluralAsSingular == word && result != word) | ||
return word; | ||
|
||
return result ?? word; | ||
} | ||
|
||
private string ApplyRules(IList<Rule> rules, string word) | ||
{ | ||
if (word == null) | ||
return null; | ||
|
||
if (IsUncountable(word)) | ||
return word; | ||
|
||
var result = word; | ||
for (int i = rules.Count - 1; i >= 0; i--) | ||
{ | ||
if ((result = rules[i].Apply(word)) != null) | ||
{ | ||
break; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private bool IsUncountable(string word) | ||
{ | ||
return _uncountables.Contains(word.ToLower()); | ||
} | ||
|
||
private class Rule | ||
{ | ||
private readonly Regex _regex; | ||
private readonly string _replacement; | ||
|
||
public Rule(string pattern, string replacement) | ||
{ | ||
_regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptionsUtil.Compiled); | ||
_replacement = replacement; | ||
} | ||
|
||
public string Apply(string word) | ||
{ | ||
if (!_regex.IsMatch(word)) | ||
{ | ||
return null; | ||
} | ||
|
||
return _regex.Replace(word, _replacement); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please remove this default constructor from public surface?
This class isn't intended to have any instance, right?