diff --git a/src/Humanizer.Tests/WordsToNumberTest.cs b/src/Humanizer.Tests/WordsToNumberTest.cs new file mode 100644 index 000000000..dde0fc65c --- /dev/null +++ b/src/Humanizer.Tests/WordsToNumberTest.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Humanizer.Tests +{ + [UseCulture("en-US")] + public class WordsToNumberTest + { + [InlineData("one", 1)] + [InlineData("minus five", -5)] + [InlineData("eleven", 11)] + [InlineData("ninety five", 95)] + [InlineData("hundred five", 105)] + [InlineData("one hundred ninety six", 196)] + [Theory] + public void ToNumber(string words, int expectedNumber) + { + Assert.Equal(expectedNumber, words.ToNumber()); + } + } +} diff --git a/src/Humanizer.sln b/src/Humanizer.sln index 05683f108..4fb0740ca 100644 --- a/src/Humanizer.sln +++ b/src/Humanizer.sln @@ -80,6 +80,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{AA44 ..\NuSpecs\Humanizer.nuspec = ..\NuSpecs\Humanizer.nuspec EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Humanizer.Tests.Shared\Humanizer.Tests.Shared.projitems*{f886a8da-3efc-4a89-91dd-06faf13da172}*SharedItemsImports = 4 @@ -128,6 +130,22 @@ Global {511A7984-F455-4A6E-ADB9-9CAAC47EA079}.Release|x64.Build.0 = Release|Any CPU {511A7984-F455-4A6E-ADB9-9CAAC47EA079}.Release|x86.ActiveCfg = Release|Any CPU {511A7984-F455-4A6E-ADB9-9CAAC47EA079}.Release|x86.Build.0 = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|ARM.ActiveCfg = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|ARM.Build.0 = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|x64.Build.0 = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Debug|x86.Build.0 = Debug|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|Any CPU.Build.0 = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|ARM.ActiveCfg = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|ARM.Build.0 = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|x64.ActiveCfg = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|x64.Build.0 = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|x86.ActiveCfg = Release|Any CPU + {4F3AF670-463C-4C9D-89B0-F569ECD4D5B9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Humanizer/Configuration/Configurator.cs b/src/Humanizer/Configuration/Configurator.cs index 15b56bb4a..16e552a2f 100644 --- a/src/Humanizer/Configuration/Configurator.cs +++ b/src/Humanizer/Configuration/Configurator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Reflection; using Humanizer.DateTimeHumanizeStrategy; @@ -35,6 +35,9 @@ public static LocaliserRegistry Formatters } private static readonly LocaliserRegistry _numberToWordsConverters = new NumberToWordsConverterRegistry(); + + private static readonly LocaliserRegistry _wordsToNumberConverters = new WordsToNumberConverterRegistry(); + /// /// A registry of number to words converters used to localise ToWords and ToOrdinalWords methods /// @@ -43,6 +46,11 @@ public static LocaliserRegistry NumberToWordsConverters get { return _numberToWordsConverters; } } + public static LocaliserRegistry WordsToNumberConverters + { + get { return _wordsToNumberConverters; } + } + private static readonly LocaliserRegistry _ordinalizers = new OrdinalizerRegistry(); /// /// A registry of ordinalizers used to localise Ordinalize method @@ -87,6 +95,11 @@ internal static INumberToWordsConverter GetNumberToWordsConverter(CultureInfo cu return NumberToWordsConverters.ResolveForCulture(culture); } + internal static IWordsToNumberConverter GetWordsToNumberConverter(CultureInfo culture) + { + return WordsToNumberConverters.ResolveForCulture(culture); + } + /// /// The ordinalizer to be used /// @@ -131,6 +144,8 @@ public static IDateTimeOffsetHumanizeStrategy DateTimeOffsetHumanizeStrategy private static readonly Func DefaultEnumDescriptionPropertyLocator = p => p.Name == "Description"; private static Func _enumDescriptionPropertyLocator = DefaultEnumDescriptionPropertyLocator; + + /// /// A predicate function for description property of attribute to use for Enum.Humanize /// @@ -139,5 +154,7 @@ public static Func EnumDescriptionPropertyLocator get { return _enumDescriptionPropertyLocator; } set { _enumDescriptionPropertyLocator = value ?? DefaultEnumDescriptionPropertyLocator; } } + + } } diff --git a/src/Humanizer/Configuration/DefaultWordsToNumberConverter.cs b/src/Humanizer/Configuration/DefaultWordsToNumberConverter.cs new file mode 100644 index 000000000..4c5a5ba04 --- /dev/null +++ b/src/Humanizer/Configuration/DefaultWordsToNumberConverter.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using Humanizer.Localisation.WordsToNumber; + +namespace Humanizer.Configuration +{ + internal class DefaultWordsToNumberConverter : GenderlessWordsToNumberConverter + { + private readonly CultureInfo _culture; + + public DefaultWordsToNumberConverter(CultureInfo culture) + { + _culture = culture; + } + + public override int Convert(string words) + { + return words.ToNumber(_culture); + } + } +} diff --git a/src/Humanizer/Configuration/IWordsToNumberConverter.cs b/src/Humanizer/Configuration/IWordsToNumberConverter.cs new file mode 100644 index 000000000..a77085def --- /dev/null +++ b/src/Humanizer/Configuration/IWordsToNumberConverter.cs @@ -0,0 +1,7 @@ +namespace Humanizer.Configuration +{ + public interface IWordsToNumberConverter + { + int Convert(string words); + } +} diff --git a/src/Humanizer/Configuration/WordsToNumberConverterRegistry.cs b/src/Humanizer/Configuration/WordsToNumberConverterRegistry.cs new file mode 100644 index 000000000..541357279 --- /dev/null +++ b/src/Humanizer/Configuration/WordsToNumberConverterRegistry.cs @@ -0,0 +1,13 @@ +using Humanizer.Localisation.WordsToNumber; + +namespace Humanizer.Configuration +{ + internal class WordsToNumberConverterRegistry : LocaliserRegistry + { + public WordsToNumberConverterRegistry() + : base((culture) => new DefaultWordsToNumberConverter(culture)) + { + Register("en", new EnglishWordsToNumberConverter()); + } + } +} diff --git a/src/Humanizer/Humanizer.csproj b/src/Humanizer/Humanizer.csproj index 2e9a2bfc3..c0ac4a119 100644 --- a/src/Humanizer/Humanizer.csproj +++ b/src/Humanizer/Humanizer.csproj @@ -12,5 +12,8 @@ true Humanizer.snk embedded + + Library + \ No newline at end of file diff --git a/src/Humanizer/Localisation/WordsToNumber/EnglishWordsToNumberConverter.cs b/src/Humanizer/Localisation/WordsToNumber/EnglishWordsToNumberConverter.cs new file mode 100644 index 000000000..733514c4c --- /dev/null +++ b/src/Humanizer/Localisation/WordsToNumber/EnglishWordsToNumberConverter.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using Humanizer.Configuration; + +namespace Humanizer.Localisation.WordsToNumber +{ + internal class EnglishWordsToNumberConverter : GenderlessWordsToNumberConverter + { + private static readonly Dictionary NumbersMap = new Dictionary + { + {"zero",0}, + {"one",1}, + {"two",2}, + {"three",3}, + {"four",4}, + {"five",5}, + {"six",6}, + {"seven",7}, + {"eight",8}, + {"nine",9}, + {"ten",10}, + {"eleven",11}, + {"twelve",12}, + {"thirteen",13}, + {"fourteen",14}, + {"fifteen",15}, + {"sixteen",16}, + {"seventeen",17}, + {"eighteen",18}, + {"nineteen",19}, + {"twenty", 20 }, + {"thirty", 30 }, + {"forty", 40 }, + {"fifty", 50 }, + {"sixty", 60 }, + {"seventy", 70 }, + {"eighty", 80 }, + {"ninety", 90 }, + {"hundred", 100 }, + {"thousand", 1000 }, + {"million", 1000000 }, + {"billion", 1000000000 } + }; + + public override int Convert(string words) + { + bool isNegative = false; + if (words.StartsWith("minus")) + { + isNegative = true; + words = words.Remove(0, 6); + } + + string[] wordsArray = words.Split(' '); + + int response = NumbersMap[wordsArray[0]]; + + for (int i = 1; i < wordsArray.Length; i++) + { + if(response < NumbersMap[wordsArray[i]]) + { + response *= NumbersMap[wordsArray[i]]; + } + else + { + response += NumbersMap[wordsArray[i]]; + } + + } + + if(isNegative) + { + return response*-1; + } + else + { + return response; + } + + + } + } +} diff --git a/src/Humanizer/Localisation/WordsToNumber/GenderlessWordsToNumberConverter.cs b/src/Humanizer/Localisation/WordsToNumber/GenderlessWordsToNumberConverter.cs new file mode 100644 index 000000000..a23bbcd3d --- /dev/null +++ b/src/Humanizer/Localisation/WordsToNumber/GenderlessWordsToNumberConverter.cs @@ -0,0 +1,9 @@ +using Humanizer.Configuration; + +namespace Humanizer.Localisation.WordsToNumber +{ + internal abstract class GenderlessWordsToNumberConverter : IWordsToNumberConverter + { + public abstract int Convert(string words); + } +} diff --git a/src/Humanizer/WordsToNumberExtension.cs b/src/Humanizer/WordsToNumberExtension.cs new file mode 100644 index 000000000..4dcabdf17 --- /dev/null +++ b/src/Humanizer/WordsToNumberExtension.cs @@ -0,0 +1,17 @@ +using System.Globalization; +using Humanizer.Configuration; + +namespace Humanizer +{ + /// + /// Transform humanized string to number; e.g. one => 1 + /// + public static class WordsToNumberExtension + { + public static int ToNumber(this string words, CultureInfo culture = null) + { + return Configurator.GetWordsToNumberConverter(culture).Convert(words); + } + + } +}