mirror of https://github.com/icsharpcode/ILSpy.git
4 changed files with 333 additions and 262 deletions
@ -0,0 +1,23 @@ |
|||||||
|
namespace Humanizer.Inflections; |
||||||
|
|
||||||
|
using CharSpan = System.ReadOnlySpan<System.Char>; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains extension methods for humanizing string values.
|
||||||
|
/// </summary>
|
||||||
|
internal static class StringHumanizeExtensions |
||||||
|
{ |
||||||
|
internal static unsafe string Concat(CharSpan left, CharSpan right) |
||||||
|
{ |
||||||
|
var result = new string('\0', left.Length + right.Length); |
||||||
|
fixed (char* pResult = result) |
||||||
|
{ |
||||||
|
left.CopyTo(new(pResult, left.Length)); |
||||||
|
right.CopyTo(new(pResult + left.Length, right.Length)); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
internal static unsafe string Concat(char left, CharSpan right) => |
||||||
|
Concat(new CharSpan(&left, 1), right); |
||||||
|
} |
@ -1,149 +1,154 @@ |
|||||||
using System; |
using System; |
||||||
using System.Threading; |
using System.Threading; |
||||||
|
|
||||||
namespace Humanizer.Inflections |
namespace Humanizer.Inflections; |
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for registered Vocabularies. At present, only a single vocabulary is supported: Default.
|
||||||
|
/// </summary>
|
||||||
|
internal static class Vocabularies |
||||||
{ |
{ |
||||||
|
static readonly Lazy<Vocabulary> Instance = new(BuildDefault, LazyThreadSafetyMode.PublicationOnly); |
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Container for registered Vocabularies. At present, only a single vocabulary is supported: Default.
|
/// 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>
|
/// </summary>
|
||||||
internal static class Vocabularies |
public static Vocabulary Default => Instance.Value; |
||||||
{ |
|
||||||
private static readonly Lazy<Vocabulary> Instance; |
|
||||||
|
|
||||||
static Vocabularies() |
|
||||||
{ |
|
||||||
Instance = new Lazy<Vocabulary>(BuildDefault, LazyThreadSafetyMode.PublicationOnly); |
|
||||||
} |
|
||||||
|
|
||||||
/// <summary>
|
static Vocabulary BuildDefault() |
||||||
/// 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().
|
var _default = new Vocabulary(); |
||||||
/// At this time, multiple vocabularies and removing existing rules are not supported.
|
|
||||||
/// </summary>
|
|
||||||
public static Vocabulary Default => Instance.Value; |
|
||||||
|
|
||||||
private static Vocabulary BuildDefault() |
|
||||||
{ |
|
||||||
var _default = new Vocabulary(); |
|
||||||
|
|
||||||
_default.AddPlural("$", "s"); |
|
||||||
_default.AddPlural("s$", "s"); |
|
||||||
_default.AddPlural("(ax|test)is$", "$1es"); |
|
||||||
_default.AddPlural("(octop|vir|alumn|fung|cact|foc|hippopotam|radi|stimul|syllab|nucle)us$", "$1i"); |
|
||||||
_default.AddPlural("(alias|bias|iris|status|campus|apparatus|virus|walrus|trellis)$", "$1es"); |
|
||||||
_default.AddPlural("(buffal|tomat|volcan|ech|embarg|her|mosquit|potat|torped|vet)o$", "$1oes"); |
|
||||||
_default.AddPlural("([dti])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|d)ix|ex$", "$1ices"); |
|
||||||
_default.AddPlural("(^[m|l])ouse$", "$1ice"); |
|
||||||
_default.AddPlural("^(ox)$", "$1en"); |
|
||||||
_default.AddPlural("(quiz)$", "$1zes"); |
|
||||||
_default.AddPlural("(buz|blit|walt)z$", "$1zes"); |
|
||||||
_default.AddPlural("(hoo|lea|loa|thie)f$", "$1ves"); |
|
||||||
_default.AddPlural("(alumn|alg|larv|vertebr)a$", "$1ae"); |
|
||||||
_default.AddPlural("(criteri|phenomen)on$", "$1a"); |
|
||||||
|
|
||||||
_default.AddSingular("s$", ""); |
_default.AddPlural("$", "s"); |
||||||
_default.AddSingular("(n)ews$", "$1ews"); |
_default.AddPlural("s$", "s"); |
||||||
_default.AddSingular("([dti])a$", "$1um"); |
_default.AddPlural("(ax|test)is$", "$1es"); |
||||||
_default.AddSingular("(analy|ba|diagno|parenthe|progno|synop|the|ellip|empha|neuro|oa|paraly)ses$", "$1sis"); |
_default.AddPlural("(octop|vir|alumn|fung|cact|foc|hippopotam|radi|stimul|syllab|nucle)us$", "$1i"); |
||||||
_default.AddSingular("([^f])ves$", "$1fe"); |
_default.AddPlural("(alias|bias|iris|status|campus|apparatus|virus|walrus|trellis)$", "$1es"); |
||||||
_default.AddSingular("(hive)s$", "$1"); |
_default.AddPlural("(buffal|tomat|volcan|ech|embarg|her|mosquit|potat|torped|vet)o$", "$1oes"); |
||||||
_default.AddSingular("(tive)s$", "$1"); |
_default.AddPlural("([dti])um$", "$1a"); |
||||||
_default.AddSingular("([lr]|hoo|lea|loa|thie)ves$", "$1f"); |
_default.AddPlural("sis$", "ses"); |
||||||
_default.AddSingular("(^zomb)?([^aeiouy]|qu)ies$", "$2y"); |
_default.AddPlural("(?:([^f])fe|([lr])f)$", "$1$2ves"); |
||||||
_default.AddSingular("(s)eries$", "$1eries"); |
_default.AddPlural("(hive)$", "$1s"); |
||||||
_default.AddSingular("(m)ovies$", "$1ovie"); |
_default.AddPlural("([^aeiouy]|qu)y$", "$1ies"); |
||||||
_default.AddSingular("(x|ch|ss|sh)es$", "$1"); |
_default.AddPlural("(x|ch|ss|sh)$", "$1es"); |
||||||
_default.AddSingular("(^[m|l])ice$", "$1ouse"); |
_default.AddPlural("(matr|vert|ind|d)(ix|ex)$", "$1ices"); |
||||||
_default.AddSingular("(o)es$", "$1"); |
_default.AddPlural("(^[m|l])ouse$", "$1ice"); |
||||||
_default.AddSingular("(shoe)s$", "$1"); |
_default.AddPlural("^(ox)$", "$1en"); |
||||||
_default.AddSingular("(cris|ax|test)es$", "$1is"); |
_default.AddPlural("(quiz)$", "$1zes"); |
||||||
_default.AddSingular("(octop|vir|alumn|fung|cact|foc|hippopotam|radi|stimul|syllab|nucle)i$", "$1us"); |
_default.AddPlural("(buz|blit|walt)z$", "$1zes"); |
||||||
_default.AddSingular("(alias|bias|iris|status|campus|apparatus|virus|walrus|trellis)es$", "$1"); |
_default.AddPlural("(hoo|lea|loa|thie)f$", "$1ves"); |
||||||
_default.AddSingular("^(ox)en", "$1"); |
_default.AddPlural("(alumn|alg|larv|vertebr)a$", "$1ae"); |
||||||
_default.AddSingular("(matr|d)ices$", "$1ix"); |
_default.AddPlural("(criteri|phenomen)on$", "$1a"); |
||||||
_default.AddSingular("(vert|ind)ices$", "$1ex"); |
|
||||||
_default.AddSingular("(quiz)zes$", "$1"); |
|
||||||
_default.AddSingular("(buz|blit|walt)zes$", "$1z"); |
|
||||||
_default.AddSingular("(alumn|alg|larv|vertebr)ae$", "$1a"); |
|
||||||
_default.AddSingular("(criteri|phenomen)a$", "$1on"); |
|
||||||
_default.AddSingular("([b|r|c]ook|room|smooth)ies$", "$1ie"); |
|
||||||
|
|
||||||
_default.AddIrregular("person", "people"); |
_default.AddSingular("s$", ""); |
||||||
_default.AddIrregular("man", "men"); |
_default.AddSingular("(n)ews$", "$1ews"); |
||||||
_default.AddIrregular("human", "humans"); |
_default.AddSingular("([dti])a$", "$1um"); |
||||||
_default.AddIrregular("child", "children"); |
_default.AddSingular("(analy|ba|diagno|parenthe|progno|synop|the|ellip|empha|neuro|oa|paraly)ses$", "$1sis"); |
||||||
_default.AddIrregular("sex", "sexes"); |
_default.AddSingular("([^f])ves$", "$1fe"); |
||||||
_default.AddIrregular("glove", "gloves"); |
_default.AddSingular("(hive)s$", "$1"); |
||||||
_default.AddIrregular("move", "moves"); |
_default.AddSingular("(tive)s$", "$1"); |
||||||
_default.AddIrregular("goose", "geese"); |
_default.AddSingular("([lr]|hoo|lea|loa|thie)ves$", "$1f"); |
||||||
_default.AddIrregular("wave", "waves"); |
_default.AddSingular("(^zomb)?([^aeiouy]|qu)ies$", "$2y"); |
||||||
_default.AddIrregular("die", "dice"); |
_default.AddSingular("(s)eries$", "$1eries"); |
||||||
_default.AddIrregular("foot", "feet"); |
_default.AddSingular("(m)ovies$", "$1ovie"); |
||||||
_default.AddIrregular("tooth", "teeth"); |
_default.AddSingular("(x|ch|ss|sh)es$", "$1"); |
||||||
_default.AddIrregular("curriculum", "curricula"); |
_default.AddSingular("(^[m|l])ice$", "$1ouse"); |
||||||
_default.AddIrregular("database", "databases"); |
_default.AddSingular("(?<!^[a-z])(o)es$", "$1"); |
||||||
_default.AddIrregular("zombie", "zombies"); |
_default.AddSingular("(shoe)s$", "$1"); |
||||||
_default.AddIrregular("personnel", "personnel"); |
_default.AddSingular("(cris|ax|test)es$", "$1is"); |
||||||
//Fix #789
|
_default.AddSingular("(octop|vir|alumn|fung|cact|foc|hippopotam|radi|stimul|syllab|nucle)i$", "$1us"); |
||||||
_default.AddIrregular("cache", "caches"); |
_default.AddSingular("(alias|bias|iris|status|campus|apparatus|virus|walrus|trellis)es$", "$1"); |
||||||
|
_default.AddSingular("^(ox)en", "$1"); |
||||||
|
_default.AddSingular("(matr|d)ices$", "$1ix"); |
||||||
|
_default.AddSingular("(vert|ind)ices$", "$1ex"); |
||||||
|
_default.AddSingular("(quiz)zes$", "$1"); |
||||||
|
_default.AddSingular("(buz|blit|walt)zes$", "$1z"); |
||||||
|
_default.AddSingular("(alumn|alg|larv|vertebr)ae$", "$1a"); |
||||||
|
_default.AddSingular("(criteri|phenomen)a$", "$1on"); |
||||||
|
_default.AddSingular("([b|r|c]ook|room|smooth)ies$", "$1ie"); |
||||||
|
|
||||||
//Fix 975
|
_default.AddIrregular("person", "people"); |
||||||
_default.AddIrregular("ex", "exes", matchEnding: false); |
_default.AddIrregular("man", "men"); |
||||||
|
_default.AddIrregular("human", "humans"); |
||||||
|
_default.AddIrregular("child", "children"); |
||||||
|
_default.AddIrregular("sex", "sexes"); |
||||||
|
_default.AddIrregular("glove", "gloves"); |
||||||
|
_default.AddIrregular("move", "moves"); |
||||||
|
_default.AddIrregular("goose", "geese"); |
||||||
|
_default.AddIrregular("wave", "waves"); |
||||||
|
_default.AddIrregular("foot", "feet"); |
||||||
|
_default.AddIrregular("tooth", "teeth"); |
||||||
|
_default.AddIrregular("curriculum", "curricula"); |
||||||
|
_default.AddIrregular("database", "databases"); |
||||||
|
_default.AddIrregular("zombie", "zombies"); |
||||||
|
_default.AddIrregular("personnel", "personnel"); |
||||||
|
_default.AddIrregular("cache", "caches"); |
||||||
|
_default.AddIrregular("ex", "exes", matchEnding: false); |
||||||
|
_default.AddIrregular("is", "are", matchEnding: false); |
||||||
|
_default.AddIrregular("was", "were", matchEnding: false); |
||||||
|
_default.AddIrregular("that", "those", matchEnding: false); |
||||||
|
_default.AddIrregular("this", "these", matchEnding: false); |
||||||
|
_default.AddIrregular("bus", "buses", matchEnding: false); |
||||||
|
_default.AddIrregular("die", "dice", matchEnding: false); |
||||||
|
_default.AddIrregular("tie", "ties", matchEnding: false); |
||||||
|
_default.AddIrregular("lens", "lenses"); |
||||||
|
_default.AddIrregular("clove", "cloves"); |
||||||
|
_default.AddIrregular("valve", "valves"); |
||||||
|
_default.AddIrregular("explosive", "explosives"); |
||||||
|
|
||||||
_default.AddIrregular("is", "are", matchEnding: false); |
_default.AddUncountable("staff"); |
||||||
_default.AddIrregular("that", "those", matchEnding: false); |
_default.AddUncountable("training"); |
||||||
_default.AddIrregular("this", "these", matchEnding: false); |
_default.AddUncountable("equipment"); |
||||||
_default.AddIrregular("bus", "buses", matchEnding: false); |
_default.AddUncountable("information"); |
||||||
_default.AddIrregular("staff", "staff", matchEnding: false); |
_default.AddUncountable("corn"); |
||||||
_default.AddIrregular("training", "training", matchEnding: false); |
_default.AddUncountable("milk"); |
||||||
|
_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"); |
||||||
|
_default.AddUncountable("water"); |
||||||
|
_default.AddUncountable("waters"); |
||||||
|
_default.AddUncountable("semen"); |
||||||
|
_default.AddUncountable("sperm"); |
||||||
|
_default.AddUncountable("bison"); |
||||||
|
_default.AddUncountable("grass"); |
||||||
|
_default.AddUncountable("hair"); |
||||||
|
_default.AddUncountable("mud"); |
||||||
|
_default.AddUncountable("elk"); |
||||||
|
_default.AddUncountable("luggage"); |
||||||
|
_default.AddUncountable("moose"); |
||||||
|
_default.AddUncountable("offspring"); |
||||||
|
_default.AddUncountable("salmon"); |
||||||
|
_default.AddUncountable("shrimp"); |
||||||
|
_default.AddUncountable("someone"); |
||||||
|
_default.AddUncountable("swine"); |
||||||
|
_default.AddUncountable("trout"); |
||||||
|
_default.AddUncountable("tuna"); |
||||||
|
_default.AddUncountable("corps"); |
||||||
|
_default.AddUncountable("scissors"); |
||||||
|
_default.AddUncountable("means"); |
||||||
|
_default.AddUncountable("mail"); |
||||||
|
_default.AddUncountable("pliers"); |
||||||
|
_default.AddUncountable("sheers"); |
||||||
|
_default.AddUncountable("clothes"); |
||||||
|
_default.AddUncountable("apparatus"); |
||||||
|
_default.AddUncountable("chassis"); |
||||||
|
_default.AddUncountable("debris"); |
||||||
|
|
||||||
_default.AddUncountable("equipment"); |
//Fix 1132
|
||||||
_default.AddUncountable("information"); |
_default.AddUncountable("metadata"); |
||||||
_default.AddUncountable("corn"); |
|
||||||
_default.AddUncountable("milk"); |
|
||||||
_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"); |
|
||||||
_default.AddUncountable("water"); |
|
||||||
_default.AddUncountable("waters"); |
|
||||||
_default.AddUncountable("semen"); |
|
||||||
_default.AddUncountable("sperm"); |
|
||||||
_default.AddUncountable("bison"); |
|
||||||
_default.AddUncountable("grass"); |
|
||||||
_default.AddUncountable("hair"); |
|
||||||
_default.AddUncountable("mud"); |
|
||||||
_default.AddUncountable("elk"); |
|
||||||
_default.AddUncountable("luggage"); |
|
||||||
_default.AddUncountable("moose"); |
|
||||||
_default.AddUncountable("offspring"); |
|
||||||
_default.AddUncountable("salmon"); |
|
||||||
_default.AddUncountable("shrimp"); |
|
||||||
_default.AddUncountable("someone"); |
|
||||||
_default.AddUncountable("swine"); |
|
||||||
_default.AddUncountable("trout"); |
|
||||||
_default.AddUncountable("tuna"); |
|
||||||
_default.AddUncountable("corps"); |
|
||||||
_default.AddUncountable("scissors"); |
|
||||||
_default.AddUncountable("means"); |
|
||||||
_default.AddUncountable("mail"); |
|
||||||
|
|
||||||
return _default; |
return _default; |
||||||
} |
|
||||||
} |
} |
||||||
} |
} |
@ -1,178 +1,220 @@ |
|||||||
using System.Collections.Generic; |
using System; |
||||||
|
using System.Collections.Generic; |
||||||
|
using System.Diagnostics.CodeAnalysis; |
||||||
using System.Text.RegularExpressions; |
using System.Text.RegularExpressions; |
||||||
|
|
||||||
namespace Humanizer.Inflections |
namespace Humanizer.Inflections; |
||||||
|
|
||||||
|
#nullable enable |
||||||
|
|
||||||
|
/// <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>
|
||||||
|
internal class Vocabulary |
||||||
{ |
{ |
||||||
|
internal Vocabulary() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
readonly List<Rule> plurals = []; |
||||||
|
readonly List<Rule> singulars = []; |
||||||
|
readonly HashSet<string> uncountables = new(StringComparer.CurrentCultureIgnoreCase); |
||||||
|
readonly Regex letterS = new("^([sS])[sS]*$"); |
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A container for exceptions to simple pluralization/singularization rules.
|
/// Adds a word to the vocabulary which cannot easily be pluralized/singularized by RegEx, e.g. "person" and "people".
|
||||||
/// Vocabularies.Default contains an extensive list of rules for US English.
|
|
||||||
/// At this time, multiple vocabularies and removing existing rules are not supported.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class Vocabulary |
/// <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>
|
||||||
|
/// <param name="matchEnding">True to match these words on their own as well as at the end of longer words. False, otherwise.</param>
|
||||||
|
public void AddIrregular(string singular, string plural, bool matchEnding = true) |
||||||
{ |
{ |
||||||
internal Vocabulary() |
if (matchEnding) |
||||||
{ |
{ |
||||||
|
var singularSubstring = singular.Substring(1); |
||||||
|
var pluralSubString = plural.Substring(1); |
||||||
|
AddPlural($"({singular[0]}){singularSubstring}$", $"$1{pluralSubString}"); |
||||||
|
AddSingular($"({plural[0]}){pluralSubString}$", $"$1{singularSubstring}"); |
||||||
} |
} |
||||||
|
else |
||||||
|
{ |
||||||
|
AddPlural($"^{singular}$", plural); |
||||||
|
AddSingular($"^{plural}$", singular); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
private readonly List<Rule> _plurals = new List<Rule>(); |
/// <summary>
|
||||||
private readonly List<Rule> _singulars = new List<Rule>(); |
/// Adds an uncountable word to the vocabulary, e.g. "fish". Will be ignored when plurality is changed.
|
||||||
private readonly List<string> _uncountables = new List<string>(); |
/// </summary>
|
||||||
|
/// <param name="word">Word to be added to the list of uncountables.</param>
|
||||||
|
public void AddUncountable(string word) => |
||||||
|
uncountables.Add(word); |
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a word to the vocabulary which cannot easily be pluralized/singularized by RegEx, e.g. "person" and "people".
|
/// Adds a rule to the vocabulary that does not follow trivial rules for pluralization, e.g. "bus" -> "buses"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="singular">The singular form of the irregular word, e.g. "person".</param>
|
/// <param name="rule">RegEx to be matched, case insensitive, e.g. "(bus)es$"</param>
|
||||||
/// <param name="plural">The plural form of the irregular word, e.g. "people".</param>
|
/// <param name="replacement">RegEx replacement e.g. "$1"</param>
|
||||||
/// <param name="matchEnding">True to match these words on their own as well as at the end of longer words. False, otherwise.</param>
|
public void AddPlural(string rule, string replacement) => |
||||||
public void AddIrregular(string singular, string plural, bool matchEnding = true) |
plurals.Add(new(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, 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>
|
||||||
|
[return: NotNullIfNotNull(nameof(word))] |
||||||
|
public string? Pluralize(string? word, bool inputIsKnownToBeSingular = true) |
||||||
|
{ |
||||||
|
if (word == null) |
||||||
{ |
{ |
||||||
if (matchEnding) |
return null; |
||||||
{ |
|
||||||
AddPlural("(" + singular[0] + ")" + singular.Substring(1) + "$", "$1" + plural.Substring(1)); |
|
||||||
AddSingular("(" + plural[0] + ")" + plural.Substring(1) + "$", "$1" + singular.Substring(1)); |
|
||||||
} |
|
||||||
else |
|
||||||
{ |
|
||||||
AddPlural($"^{singular}$", plural); |
|
||||||
AddSingular($"^{plural}$", singular); |
|
||||||
} |
|
||||||
} |
} |
||||||
|
|
||||||
/// <summary>
|
var s = LetterS(word); |
||||||
/// Adds an uncountable word to the vocabulary, e.g. "fish". Will be ignored when plurality is changed.
|
if (s != null) |
||||||
/// </summary>
|
|
||||||
/// <param name="word">Word to be added to the list of uncountables.</param>
|
|
||||||
public void AddUncountable(string word) |
|
||||||
{ |
{ |
||||||
_uncountables.Add(word.ToLower()); |
return s + "s"; |
||||||
} |
} |
||||||
|
|
||||||
/// <summary>
|
var result = ApplyRules(plurals, word, false); |
||||||
/// Adds a rule to the vocabulary that does not follow trivial rules for pluralization, e.g. "bus" -> "buses"
|
|
||||||
/// </summary>
|
if (inputIsKnownToBeSingular) |
||||||
/// <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)); |
return result ?? word; |
||||||
} |
} |
||||||
|
|
||||||
/// <summary>
|
var asSingular = ApplyRules(singulars, word, false); |
||||||
/// Adds a rule to the vocabulary that does not follow trivial rules for singularization, e.g. "vertices/indices -> "vertex/index"
|
var asSingularAsPlural = ApplyRules(plurals, asSingular, false); |
||||||
/// </summary>
|
if (asSingular != null && |
||||||
/// <param name="rule">RegEx to be matched, case insensitive, e.g. ""(vert|ind)ices$""</param>
|
asSingular != word && |
||||||
/// <param name="replacement">RegEx replacement e.g. "$1ex"</param>
|
asSingular + "s" != word && |
||||||
public void AddSingular(string rule, string replacement) |
asSingularAsPlural == word && |
||||||
|
result != word) |
||||||
{ |
{ |
||||||
_singulars.Add(new Rule(rule, replacement)); |
return word; |
||||||
} |
} |
||||||
|
|
||||||
/// <summary>
|
return result!; |
||||||
/// 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, false); |
|
||||||
|
|
||||||
if (inputIsKnownToBeSingular) |
/// <summary>
|
||||||
{ |
/// Singularizes the provided input considering irregular words
|
||||||
return result ?? word; |
/// </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>
|
||||||
|
/// <param name="skipSimpleWords">Skip singularizing single words that have an 's' on the end</param>
|
||||||
|
[return: NotNullIfNotNull(nameof(word))] |
||||||
|
public string? Singularize(string? word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) |
||||||
|
{ |
||||||
|
if (word == null) |
||||||
|
{ |
||||||
|
return null; |
||||||
|
} |
||||||
|
var s = LetterS(word); |
||||||
|
if (s != null) |
||||||
|
{ |
||||||
|
return s; |
||||||
|
} |
||||||
|
|
||||||
var asSingular = ApplyRules(_singulars, word, false); |
var result = ApplyRules(singulars, word, skipSimpleWords); |
||||||
var asSingularAsPlural = ApplyRules(_plurals, asSingular, false); |
|
||||||
if (asSingular != null && asSingular != word && asSingular + "s" != word && asSingularAsPlural == word && result != word) |
|
||||||
{ |
|
||||||
return word; |
|
||||||
} |
|
||||||
|
|
||||||
return result; |
if (inputIsKnownToBePlural) |
||||||
|
{ |
||||||
|
return result ?? word; |
||||||
} |
} |
||||||
|
|
||||||
/// <summary>
|
// the Plurality is unknown so we should check all possibilities
|
||||||
/// Singularizes the provided input considering irregular words
|
var asPlural = ApplyRules(plurals, word, false); |
||||||
/// </summary>
|
if (asPlural == word || |
||||||
/// <param name="word">Word to be singularized</param>
|
word + "s" == asPlural) |
||||||
/// <param name="inputIsKnownToBePlural">Normally you call Singularize on plural words; but if you're unsure call it with false</param>
|
|
||||||
/// <param name="skipSimpleWords">Skip singularizing single words that have an 's' on the end</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string Singularize(string word, bool inputIsKnownToBePlural = true, bool skipSimpleWords = false) |
|
||||||
{ |
{ |
||||||
var result = ApplyRules(_singulars, word, skipSimpleWords); |
return result ?? word; |
||||||
|
} |
||||||
if (inputIsKnownToBePlural) |
|
||||||
{ |
|
||||||
return result ?? word; |
|
||||||
} |
|
||||||
|
|
||||||
// the Plurality is unknown so we should check all possibilities
|
|
||||||
var asPlural = ApplyRules(_plurals, word, false); |
|
||||||
var asPluralAsSingular = ApplyRules(_singulars, asPlural, false); |
|
||||||
if (asPlural != word && word + "s" != asPlural && asPluralAsSingular == word && result != word) |
|
||||||
{ |
|
||||||
return word; |
|
||||||
} |
|
||||||
|
|
||||||
|
var asPluralAsSingular = ApplyRules(singulars, asPlural, false); |
||||||
|
if (asPluralAsSingular != word || |
||||||
|
result == word) |
||||||
|
{ |
||||||
return result ?? word; |
return result ?? word; |
||||||
} |
} |
||||||
|
|
||||||
private string ApplyRules(IList<Rule> rules, string word, bool skipFirstRule) |
return word; |
||||||
|
} |
||||||
|
|
||||||
|
string? ApplyRules(IList<Rule> rules, string? word, bool skipFirstRule) |
||||||
|
{ |
||||||
|
if (word == null) |
||||||
{ |
{ |
||||||
if (word == null) |
return null; |
||||||
{ |
} |
||||||
return null; |
|
||||||
} |
|
||||||
|
|
||||||
if (word.Length < 1) |
if (word.Length < 1) |
||||||
{ |
{ |
||||||
return word; |
return word; |
||||||
} |
} |
||||||
|
|
||||||
if (IsUncountable(word)) |
if (IsUncountable(word)) |
||||||
{ |
{ |
||||||
return word; |
return word; |
||||||
} |
} |
||||||
|
|
||||||
var result = word; |
var result = word; |
||||||
var end = skipFirstRule ? 1 : 0; |
var end = skipFirstRule ? 1 : 0; |
||||||
for (var i = rules.Count - 1; i >= end; i--) |
for (var i = rules.Count - 1; i >= end; i--) |
||||||
|
{ |
||||||
|
if ((result = rules[i].Apply(word)) != null) |
||||||
{ |
{ |
||||||
if ((result = rules[i].Apply(word)) != null) |
break; |
||||||
{ |
|
||||||
break; |
|
||||||
} |
|
||||||
} |
} |
||||||
return result; |
|
||||||
} |
} |
||||||
|
|
||||||
private bool IsUncountable(string word) |
if (result == null) |
||||||
{ |
{ |
||||||
return _uncountables.Contains(word.ToLower()); |
return null; |
||||||
} |
} |
||||||
|
|
||||||
private class Rule |
return MatchUpperCase(word, result); |
||||||
{ |
} |
||||||
private readonly Regex _regex; |
|
||||||
private readonly string _replacement; |
|
||||||
|
|
||||||
public Rule(string pattern, string replacement) |
bool IsUncountable(string word) => |
||||||
{ |
uncountables.Contains(word); |
||||||
_regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); |
|
||||||
_replacement = replacement; |
|
||||||
} |
|
||||||
|
|
||||||
public string Apply(string word) |
static string MatchUpperCase(string word, string replacement) => |
||||||
{ |
char.IsUpper(word[0]) && |
||||||
if (!_regex.IsMatch(word)) |
char.IsLower(replacement[0]) ? StringHumanizeExtensions.Concat(char.ToUpper(replacement[0]), replacement.AsSpan(1)) : replacement; |
||||||
{ |
|
||||||
return null; |
/// <summary>
|
||||||
} |
/// If the word is the letter s, singular or plural, return the letter s singular
|
||||||
|
/// </summary>
|
||||||
|
string? LetterS(string word) |
||||||
|
{ |
||||||
|
var s = letterS.Match(word); |
||||||
|
return s.Groups.Count > 1 ? s.Groups[1].Value : null; |
||||||
|
} |
||||||
|
|
||||||
|
class Rule(string pattern, string replacement) |
||||||
|
{ |
||||||
|
readonly Regex regex = new(pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); |
||||||
|
|
||||||
return _regex.Replace(word, _replacement); |
public string? Apply(string word) |
||||||
|
{ |
||||||
|
if (!regex.IsMatch(word)) |
||||||
|
{ |
||||||
|
return null; |
||||||
} |
} |
||||||
|
|
||||||
|
return regex.Replace(word, replacement); |
||||||
} |
} |
||||||
} |
} |
||||||
} |
} |
Loading…
Reference in new issue