/// <summary> /// Create a rule from the specified <see cref="XPathNavigator"/>. /// </summary> /// <param name="xml"> /// The XML representation of a rule based number format. /// </param> /// <returns> /// A new rule. /// </returns> /// <remarks> /// The <paramref name="xml"/> must be on an "rbnfrule" element. /// </remarks> public static IRule Parse(XPathNavigator xml) { Rule rule = null; var value = xml.GetAttribute("value", ""); var radix = xml.GetAttribute("radix", ""); int radixNumber = radix.Length == 0 ? 10 : Int32.Parse(radix, CultureInfo.InvariantCulture); switch (value) { case "-x": rule = new NegativeNumberRule(); break; case "Inf": rule = new InfinityRule(); break; case "NaN": rule = new NanRule(); break; case "x.x": rule = new ImproperFractionRule(); break; case "0.x": rule = new ProperFractionRule(); break; case "x.0": rule = new MasterRule(); break; default: var c = value[0]; if ('0' <= c && c <= '9') { rule = new BaseValueRule { LowerLimit = decimal.Parse(value), radix = radixNumber }; } else { throw new FormatException($"Unknown rule value '{value}'."); } break; } var body = xml.Value; rule.Substitutions = Substitution.Parse(body.TrimEnd(';')).ToArray(); return(rule); }
public void TextOnly() { var subs = Substitution.Parse("one").ToArray(); Assert.AreEqual(1, subs.Length); Assert.AreEqual("", subs[0].Descriptor); Assert.AreEqual("one", subs[0].Text); Assert.AreEqual("", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); }
public void NumberPattern() { var subs = Substitution.Parse("=#,##0=").ToArray(); Assert.AreEqual(1, subs.Length); Assert.AreEqual("#,##0", subs[0].Descriptor); Assert.AreEqual("", subs[0].Text); Assert.AreEqual("=", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); }
public void Text_Recurse_GT2() { var subs = Substitution.Parse("-→→").ToArray(); Assert.AreEqual(2, subs.Length); Assert.AreEqual("", subs[0].Descriptor); Assert.AreEqual("-", subs[0].Text); Assert.AreEqual("", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); Assert.AreEqual("", subs[1].Descriptor); Assert.AreEqual("", subs[1].Text); Assert.AreEqual("→→", subs[1].Token); Assert.AreEqual(null, subs[1].Optionals); }
public void Text_Recurse_LT() { var subs = Substitution.Parse("←← hundred").ToArray(); Assert.AreEqual(2, subs.Length); Assert.AreEqual("", subs[0].Descriptor); Assert.AreEqual("", subs[0].Text); Assert.AreEqual("←←", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); Assert.AreEqual("", subs[1].Descriptor); Assert.AreEqual(" hundred", subs[1].Text); Assert.AreEqual("", subs[1].Token); Assert.AreEqual(null, subs[1].Optionals); }
public void Text_Transfer_to_Rule() { var subs = Substitution.Parse("e-=%%et-unieme=").ToArray(); Assert.AreEqual(2, subs.Length); Assert.AreEqual("", subs[0].Descriptor); Assert.AreEqual("e-", subs[0].Text); Assert.AreEqual("", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); Assert.AreEqual("et-unieme", subs[1].Descriptor); Assert.AreEqual("", subs[1].Text); Assert.AreEqual("=", subs[1].Token); Assert.AreEqual(null, subs[1].Optionals); }
void Apply(Substitution sub, RbnfContext context) { var number = context.Number; context.Text.Append(sub.Text); switch (sub.Token) { case "→→": context.Number = Decimal.Remainder(number, Divisor()); context.Ruleset.ApplyRules(context); break; case "←←": context.Number = Math.Floor(number / Divisor()); context.Ruleset.ApplyRules(context); break; case "=": // Fallback number formating? if (sub.Descriptor.StartsWith("#") || sub.Descriptor.StartsWith("0")) { context.Text.Append(context.Number.ToString(sub.Descriptor, CultureInfo.InvariantCulture)); } // Else goto the ruleset. else { var ruleset = context.RulesetGroup.Rulesets[sub.Descriptor]; ruleset.ApplyRules(context); } break; case "": break; default: throw new NotSupportedException($"Substitution token '{sub.Token}' is not allowed."); } if (sub.Optional != null && (number % Divisor()) != 0) { Apply(sub.Optional, context); } context.Number = number; }
public void Text_CallRule1() { var subs = Substitution.Parse("←← cent→%%cents-m→").ToArray(); Assert.AreEqual(3, subs.Length); Assert.AreEqual("", subs[0].Descriptor); Assert.AreEqual("", subs[0].Text); Assert.AreEqual("←←", subs[0].Token); Assert.AreEqual(null, subs[0].Optionals); Assert.AreEqual("", subs[1].Descriptor); Assert.AreEqual(" cent", subs[1].Text); Assert.AreEqual("", subs[1].Token); Assert.AreEqual(null, subs[1].Optionals); Assert.AreEqual("cents-m", subs[2].Descriptor); Assert.AreEqual("", subs[2].Text); Assert.AreEqual("→", subs[2].Token); Assert.AreEqual(null, subs[2].Optionals); }
/// <summary> /// Parses a rule body. /// </summary> /// <param name="s"> /// The text representation of action(s) to perform. /// </param> /// <returns> /// A sequence of actions to perform. /// </returns> public static IEnumerable <Substitution> Parse(string s) { // If a rule body begins with an apostrophe, the apostrophe is ignored, // but all text after it becomes significant (this is how you can // have a rule's rule text begin with whitespace). if (s.StartsWith("'")) { s = s.Substring(1); } for (var match = SubstitutionRegex.Match(s); match.Success; match = match.NextMatch()) { var substitution = new Substitution { Token = match.Groups["token"].Value, Text = match.Groups["text"].Value, Descriptor = "" }; if (match.Groups["desc"].Success) { var desc = match.Groups["desc"].Value; substitution.Token = desc.Substring(0, 1); desc = desc.Substring(1, desc.Length - 2); substitution.Descriptor = desc .TrimStart('%'); } if (match.Groups["opt"].Success) { var opt = match.Groups["opt"].Value; substitution.Optionals = Parse(opt).ToArray(); } // Ignore empty matches; everything is optional in the regex. //if (!string.IsNullOrEmpty(substitution.Text) || !string.IsNullOrEmpty(substitution.Token)) yield return(substitution); } }
public static IEnumerable <Substitution> Parse(string s) { for (var match = SubstitutionRegex.Match(s); match.Success; match = match.NextMatch()) { var substitution = new Substitution { Token = match.Groups["token"].Value, Text = match.Groups["text"].Value, Descriptor = match.Groups["desc"].Value }; if (match.Groups["opt"].Success) { substitution.Optional = Parse(match.Groups["opt"].Value).First(); } // Ignore empty matches; everything is optional in the regex. if (!string.IsNullOrEmpty(substitution.Text) || !string.IsNullOrEmpty(substitution.Token)) { yield return(substitution); } } }
void Apply(Substitution sub, RbnfContext context) { var number = context.Number; context.Text.Append(sub.Text); switch (sub.Token) { // Divide the number by the rule's divisor and format the remainder. case "→→": context.Number = Decimal.Remainder(number, Divisor()); context.Ruleset.ApplyRules(context); break; // Divide the number by the rule's divisor and format the quotient. case "←←": context.Number = Math.Floor(number / Divisor()); context.Ruleset.ApplyRules(context); break; case "=": // Fallback number formating? if (sub.Descriptor.StartsWith("#") || sub.Descriptor.StartsWith("0")) { var formatter = NumberFormatter.Create(context.Locale); context.Text.Append(formatter.Format(context.Number)); } // Else goto the ruleset. else { var ruleset = context.RulesetGroup.Rulesets[sub.Descriptor]; ruleset.ApplyRules(context); } break; // Divide the number by the rule's divisor and format the remainder // with the specified ruleset. case "→": context.Number = Decimal.Remainder(number, Divisor()); context.RulesetGroup .Rulesets[sub.Descriptor] .ApplyRules(context); break; // Divide the number by the rule's divisor and format the quotient // with the specified ruleset. case "←": context.Number = Math.Floor(number / Divisor()); context.RulesetGroup .Rulesets[sub.Descriptor] .ApplyRules(context); break; case "": break; default: throw new NotSupportedException($"Substitution token '{sub.Token}' is not allowed."); } if (sub.Optionals != null && (number % Divisor()) != 0) { foreach (var optional in sub.Optionals) { Apply(optional, context); } } context.Number = number; }