diff --git a/doc/Documentation.csproj b/doc/Documentation.csproj index 5b02f0b..8a09ebd 100644 --- a/doc/Documentation.csproj +++ b/doc/Documentation.csproj @@ -85,6 +85,7 @@ + Web.config diff --git a/doc/articles/numbering-systems/category.md b/doc/articles/numbering-systems/category.md index 82f2be8..8515080 100644 --- a/doc/articles/numbering-systems/category.md +++ b/doc/articles/numbering-systems/category.md @@ -1,10 +1,27 @@ # Categories -Instead of using a specific numbering system name, a category name is -used to request a locale specific number system for the category. +Instead of using a specific numbering system name, a category name can be +used to request a locale specific numbering system for the category. | Category | Description | | -------- | ----------- | | native | Requests the numbering system used for the native digits, usually defined as a part of the script used to write the language. | -| financial | Requests the numbering system used for financial quantities. This is often used for ideographic languages such as Chinese, where it would be easy to alter an amount represented in the default numbering system simply by adding additional strokes. If the financial numbering system is not specified, applications should use the default numbering system as a fallback. | -| traditional | Requests the traditional numerals for a locale. If the traditional numbering system is not defined, applications should use the native numbering system as a fallback. | +| finance | Requests the numbering system used for financial quantities. This is often used for ideographic languages such as Chinese, where it would be easy to alter an amount represented in the default numbering system simply by adding additional strokes. | +| traditio | Requests the traditional numerals for a locale. If the traditional numbering system is not defined, applications should use the native numbering system as a fallback. | + +If the locale does not define a numbering system for the category, then the default numbering system is used as a fallback. + +## Example + +The following table shows the number `123` represented in various locales. + +| Locale | Formatted | +| ------ | --------- | +| en | 123 | +| en-u-nu-native | 123 | +| en-u-nu-traditio | 123 | +| en-u-nu-finance | 123 | +| zh-TW | 123 | +| zh-TW-u-nu-native | 一二三 | +| zh-TW-u-nu-traditio | 一百二十三 | +| zh-TW-u-nu-finance | 壹佰貳拾參 | diff --git a/src/Numbers/NumberingSystem.cs b/src/Numbers/NumberingSystem.cs index 39556cd..6762241 100644 --- a/src/Numbers/NumberingSystem.cs +++ b/src/Numbers/NumberingSystem.cs @@ -1,269 +1,269 @@ -using Common.Logging; -using Sepia.Globalization.Numbers.Rules; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Sepia.Globalization.Numbers -{ - /// - /// Representation of numeric value. - /// - public class NumberingSystem - { - static ILog log = LogManager.GetLogger(typeof(NumberingSystem)); - static ConcurrentDictionary Cache = new ConcurrentDictionary(); - static string[] Others = new[] { "native", "finance", "traditio" }; - static object safe = new Object(); - - static RulesetGroup numberingSystemRules; - static RulesetGroup NumberingSystemRules - { - get - { - if (numberingSystemRules == null) - { - lock (safe) - { - if (numberingSystemRules == null) - { - var xml = Cldr.Instance - .GetDocuments("common/rbnf/root.xml") - .FirstElement("ldml/rbnf/rulesetGrouping[@type='NumberingSystemRules']"); - numberingSystemRules = RulesetGroup.Parse(xml); - } - } - } - - return numberingSystemRules; - } - } - - RulesetGroup rulesetGroup; - Ruleset ruleset; - - /// - /// Unique identifier of the numbering system. - /// - /// - /// Such as "latn", "arab", "hanidec", etc. - /// - public string Id { get; set; } - - /// - /// The type of numbering system. - /// - /// - /// Either "algorithmic" or "numeric". - /// - /// - /// - public string Type { get; set; } - - /// - /// The digits used to represent numbers, in order, starting from zero. - /// - /// - /// Only valid when equals "numeric". - /// - /// - public string[] Digits { get; set; } - - /// - /// The RBNF ruleset name used for formatting numbers. - /// - /// - /// Only valid when equals "algorithmic". - /// - /// - /// The rules specifier can contain simply a ruleset name, in which case the ruleset is - /// assumed to be found in the rule set grouping "NumberingSystemRules". Alternatively, - /// the specifier can denote a specific locale, ruleset grouping, and ruleset name, - /// separated by slashes. - /// - /// - public string RuleName { get; set; } - - /// - /// Gets the grouping of rules for the numbering systems. - /// - /// - /// Only valid when equals "algorithmic". - /// - public RulesetGroup RulesetGroup - { - get - { - if (rulesetGroup == null) - { - lock (safe) - { - if (rulesetGroup == null) - { - if (RuleName.Contains('/')) - { - var parts = RuleName.Split('/'); - var locale = Locale.Create(parts[0]); - var xml = locale - .ResourceBundle("common/rbnf/") - .FirstElement($"ldml/rbnf/rulesetGrouping[@type='{parts[1]}']"); - rulesetGroup = RulesetGroup.Parse(xml); - ruleset = rulesetGroup.Rulesets[parts[2]]; - } - else - { - rulesetGroup = NumberingSystemRules; - } - } - } - } - - return rulesetGroup; - } - } - - /// - /// A set of rules for the numbering system. - /// - /// - /// Only valid when equals "algorithmic". - /// - public Ruleset Ruleset - { - get - { - if (ruleset == null) - { - lock (safe) - { - var group = RulesetGroup; - if (ruleset == null) - { - ruleset = group.Rulesets[RuleName]; - } - } - } - - return ruleset; - } - } - - /// - /// Determines if the is numeric. - /// - /// - /// true if equals "numeric"; otherwise, false. - /// - /// - /// Numeric systems are simply a decimal based system that uses a predefined set of - /// to represent numbers. - /// - public bool IsNumeric { get { return Type == "numeric"; } } - - /// - /// Determines if the is algorithmic. - /// - /// - /// true if equals "algorithmic"; otherwise, false. - /// - /// - /// Algorithmic systems are complex in nature, since the proper formatting and presentation - /// of a numeric quantity is based on some algorithm or set of . - /// - public bool IsAlgorithmic { get { return Type == "algorithmic"; } } - - /// - /// Creates or reuses a numbering system with the specified identifier. - /// - /// - /// A case insensitive string containing the . - /// - /// - /// The numbering system is not defined. - /// - /// - /// A numbering system for the specified . - /// - public static NumberingSystem Create(string id) - { - id = id.ToLowerInvariant(); - return Cache.GetOrAdd(id, key => - { - var xml = Cldr.Instance - .GetDocuments("common/supplemental/numberingSystems.xml") - .FirstElement($"supplementalData/numberingSystems/numberingSystem[@id='{key}']"); - if (log.IsDebugEnabled) - log.DebugFormat("Loading '{0}'", key); - return new NumberingSystem - { - Id = xml.GetAttribute("id", ""), - Type = xml.GetAttribute("type", ""), - Digits = GetTextElements(xml.GetAttribute("digits", "")).ToArray(), - RuleName = xml.GetAttribute("rules", "") - }; - }); - } - - static IEnumerable GetTextElements(string s) - { - var text = StringInfo.GetTextElementEnumerator(s); - while (text.MoveNext()) - { - yield return text.GetTextElement(); - } - } - - /// - /// Creates or reuses a numbering system for the specified . - /// - /// - /// The locale. - /// - /// - /// A numbering system that is the best for the . - /// - /// - /// The locale identifier can use the "u-nu-XXX" extension to specify a numbering system. - /// If the extension's numbering system doesn't exist or is not specified, - /// then the default numbering system for the locale is used. - /// - /// The other numbering systems - /// ("u-nu-native", "u-nu-finance" and "u-nu-traditio") are also allowed. - /// - /// - public static NumberingSystem Create(Locale locale) - { - string name; - if (locale.Id.UnicodeExtension.Keywords.TryGetValue("nu", out name)) - { - try - { - if (Others.Contains(name)) - { - // Consistency is the hobgoblin of small minds. - var other = name == "traditio" - ? "traditional" - : name; - name = locale.Find($"ldml/numbers/otherNumberingSystems/{other}").Value; - } - return NumberingSystem.Create(name); - } - catch (KeyNotFoundException) - { - // eat it, will fallback to default numbering system. - } - } - - // Find the default numbering system for the locale. - var ns = locale.Find("ldml/numbers/defaultNumberingSystem/text()").Value; - - return NumberingSystem.Create(ns); - } - - - } -} +using Common.Logging; +using Sepia.Globalization.Numbers.Rules; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Sepia.Globalization.Numbers +{ + /// + /// Representation of numeric value. + /// + public class NumberingSystem + { + static ILog log = LogManager.GetLogger(typeof(NumberingSystem)); + static ConcurrentDictionary Cache = new ConcurrentDictionary(); + static string[] Others = new[] { "native", "finance", "traditio" }; + static object safe = new Object(); + + static RulesetGroup numberingSystemRules; + static RulesetGroup NumberingSystemRules + { + get + { + if (numberingSystemRules == null) + { + lock (safe) + { + if (numberingSystemRules == null) + { + var xml = Cldr.Instance + .GetDocuments("common/rbnf/root.xml") + .FirstElement("ldml/rbnf/rulesetGrouping[@type='NumberingSystemRules']"); + numberingSystemRules = RulesetGroup.Parse(xml); + } + } + } + + return numberingSystemRules; + } + } + + RulesetGroup rulesetGroup; + Ruleset ruleset; + + /// + /// Unique identifier of the numbering system. + /// + /// + /// Such as "latn", "arab", "hanidec", etc. + /// + public string Id { get; set; } + + /// + /// The type of numbering system. + /// + /// + /// Either "algorithmic" or "numeric". + /// + /// + /// + public string Type { get; set; } + + /// + /// The digits used to represent numbers, in order, starting from zero. + /// + /// + /// Only valid when equals "numeric". + /// + /// + public string[] Digits { get; set; } + + /// + /// The RBNF ruleset name used for formatting numbers. + /// + /// + /// Only valid when equals "algorithmic". + /// + /// + /// The rules specifier can contain simply a ruleset name, in which case the ruleset is + /// assumed to be found in the rule set grouping "NumberingSystemRules". Alternatively, + /// the specifier can denote a specific locale, ruleset grouping, and ruleset name, + /// separated by slashes. + /// + /// + public string RuleName { get; set; } + + /// + /// Gets the grouping of rules for the numbering systems. + /// + /// + /// Only valid when equals "algorithmic". + /// + public RulesetGroup RulesetGroup + { + get + { + if (rulesetGroup == null) + { + lock (safe) + { + if (rulesetGroup == null) + { + if (RuleName.Contains('/')) + { + var parts = RuleName.Split('/'); + var locale = Locale.Create(parts[0]); + var xml = locale + .ResourceBundle("common/rbnf/") + .FirstElement($"ldml/rbnf/rulesetGrouping[@type='{parts[1]}']"); + rulesetGroup = RulesetGroup.Parse(xml); + ruleset = rulesetGroup.Rulesets[parts[2]]; + } + else + { + rulesetGroup = NumberingSystemRules; + } + } + } + } + + return rulesetGroup; + } + } + + /// + /// A set of rules for the numbering system. + /// + /// + /// Only valid when equals "algorithmic". + /// + public Ruleset Ruleset + { + get + { + if (ruleset == null) + { + lock (safe) + { + var group = RulesetGroup; + if (ruleset == null) + { + ruleset = group.Rulesets[RuleName]; + } + } + } + + return ruleset; + } + } + + /// + /// Determines if the is numeric. + /// + /// + /// true if equals "numeric"; otherwise, false. + /// + /// + /// Numeric systems are simply a decimal based system that uses a predefined set of + /// to represent numbers. + /// + public bool IsNumeric { get { return Type == "numeric"; } } + + /// + /// Determines if the is algorithmic. + /// + /// + /// true if equals "algorithmic"; otherwise, false. + /// + /// + /// Algorithmic systems are complex in nature, since the proper formatting and presentation + /// of a numeric quantity is based on some algorithm or set of . + /// + public bool IsAlgorithmic { get { return Type == "algorithmic"; } } + + /// + /// Creates or reuses a numbering system with the specified identifier. + /// + /// + /// A case insensitive string containing the . + /// + /// + /// The numbering system is not defined. + /// + /// + /// A numbering system for the specified . + /// + public static NumberingSystem Create(string id) + { + id = id.ToLowerInvariant(); + return Cache.GetOrAdd(id, key => + { + var xml = Cldr.Instance + .GetDocuments("common/supplemental/numberingSystems.xml") + .FirstElement($"supplementalData/numberingSystems/numberingSystem[@id='{key}']"); + if (log.IsDebugEnabled) + log.DebugFormat("Loading '{0}'", key); + return new NumberingSystem + { + Id = xml.GetAttribute("id", ""), + Type = xml.GetAttribute("type", ""), + Digits = GetTextElements(xml.GetAttribute("digits", "")).ToArray(), + RuleName = xml.GetAttribute("rules", "") + }; + }); + } + + static IEnumerable GetTextElements(string s) + { + var text = StringInfo.GetTextElementEnumerator(s); + while (text.MoveNext()) + { + yield return text.GetTextElement(); + } + } + + /// + /// Creates or reuses a numbering system for the specified . + /// + /// + /// The locale. + /// + /// + /// A numbering system that is the best for the . + /// + /// + /// The locale identifier can use the "u-nu-XXX" extension to specify a numbering system. + /// If the extension's numbering system doesn't exist or is not specified, + /// then the default numbering system for the locale is used. + /// + /// The other numbering systems + /// ("u-nu-native", "u-nu-finance" and "u-nu-traditio") are also allowed. + /// + /// + public static NumberingSystem Create(Locale locale) + { + string name; + if (locale.Id.UnicodeExtension.Keywords.TryGetValue("nu", out name)) + { + try + { + if (Others.Contains(name)) + { + // Consistency is the hobgoblin of small minds. + var other = name == "traditio" + ? "traditional" + : name; + name = locale.Find($"ldml/numbers/otherNumberingSystems/{other}").Value; + } + return NumberingSystem.Create(name); + } + catch (KeyNotFoundException) + { + // eat it, will fallback to default numbering system. + } + } + + // Find the default numbering system for the locale. + var ns = locale.Find("ldml/numbers/defaultNumberingSystem/text()").Value; + + return NumberingSystem.Create(ns); + } + + + } +} diff --git a/test/Numbers/NumberFormatterTest.cs b/test/Numbers/NumberFormatterTest.cs index 2068884..9d7b47b 100644 --- a/test/Numbers/NumberFormatterTest.cs +++ b/test/Numbers/NumberFormatterTest.cs @@ -68,5 +68,17 @@ public void Length_Example() Console.WriteLine($"| {length} | {style} | {formatter.Format(1234.56, "EUR")} |"); } } + + [TestMethod] + public void Category_Example() + { + foreach (var lang in new[] { "en", "zh-TW" }) + foreach (var cat in new[] { "", "-u-nu-native", "-u-nu-traditio", "-u-nu-finance" }) + { + var locale = Locale.Create(lang + cat); + var formatter = NumberFormatter.Create(locale); + Console.WriteLine($"| {lang + cat} | {formatter.Format(123)} |"); + } + } } }