// ----------------------------------------------------------------------- // construction // ----------------------------------------------------------------------- /* * Constructs a rule set. * * @param descriptions An array of Strings representing rule set * descriptions. On exit, this rule set's entry in the array will have been * stripped of its rule set name and any trailing whitespace. * * @param index The index into "descriptions" of the description for the * rule to be constructed */ public NFRuleSet(String[] descriptions, int index) { this.negativeNumberRule = null; this.fractionRules = new NFRule[3]; this.isFractionRuleSet = false; this.recursionCount = 0; String description = descriptions[index]; if (description.Length == 0) { throw new ArgumentException("Empty rule set description"); } // if the description begins with a rule set name (the rule set // name can be omitted in formatter descriptions that consist // of only one rule set), copy it out into our "name" member // and delete it from the description if (description[0] == '%') { int pos = description.IndexOf(':'); if (pos == -1) { throw new ArgumentException( "Rule set name doesn't end in colon"); } else { name = description.Substring(0, (pos) - (0)); while (pos < description.Length && IBM.ICU.Impl.UCharacterProperty.IsRuleWhiteSpace(description[++pos])) { } description = description.Substring(pos); descriptions[index] = description; } // if the description doesn't begin with a rule set name, its // name is "%default" } else { name = "%default"; } if (description.Length == 0) { throw new ArgumentException("Empty rule set description"); } // all of the other members of NFRuleSet are initialized // by parseRules() }
/// <summary> /// Formats a double. Selects an appropriate rule and dispatches control to /// it. /// </summary> /// /// <param name="number">The number being formatted</param> /// <param name="toInsertInto">The string where the result is to be placed</param> /// <param name="pos">The position in toInsertInto where the result of thisoperation is to be inserted</param> public void Format(double number, StringBuilder toInsertInto, int pos) { NFRule applicableRule = FindRule(number); if (++recursionCount >= RECURSION_LIMIT) { recursionCount = 0; throw new InvalidOperationException( "Recursion limit exceeded when applying ruleSet " + name); } applicableRule.DoFormat(number, toInsertInto, pos); --recursionCount; }
/// <summary> /// If the value passed to findRule() is a positive integer, findRule() uses /// this function to select the appropriate rule. The result will generally /// be the rule with the highest base value less than or equal to the number. /// There is one exception to this: If that rule has two substitutions and a /// base value that is not an even multiple of its divisor, and the number /// itself IS an even multiple of the rule's divisor, then the result will be /// the rule that preceded the original result in the rule list. (This /// behavior is known as the "rollback rule", and is used to handle optional /// text: a rule with optional text is represented internally as two rules, /// and the rollback rule selects appropriate between them. This avoids /// things like "two hundred zero".) /// </summary> /// /// <param name="number">The number being formatted</param> /// <returns>The rule to use to format this number</returns> private NFRule FindNormalRule(long number) { // if this is a fraction rule set, use findFractionRuleSetRule() // to find the rule (we should only go into this clause if the // value is 0) if (isFractionRuleSet) { return(FindFractionRuleSetRule(number)); } // if the number is negative, return the negative-number rule // (if there isn't one, pretend the number is positive) if (number < 0) { if (negativeNumberRule != null) { return(negativeNumberRule); } else { number = -number; } } // we have to repeat the preceding two checks, even though we // do them in findRule(), because the version of format() that // takes a long bypasses findRule() and goes straight to this // function. This function does skip the fraction rules since // we know the value is an integer (it also skips the master // rule, since it's considered a fraction rule. Skipping the // master rule in this function is also how we avoid infinite // recursion) // binary-search the rule list for the applicable rule // (a rule is used for all values from its base value to // the next rule's base value) int lo = 0; int hi = rules.Length; if (hi > 0) { while (lo < hi) { int mid = (lo + hi) / 2; if (rules[mid].GetBaseValue() == number) { return(rules[mid]); } else if (rules[mid].GetBaseValue() > number) { hi = mid; } else { lo = mid + 1; } } if (hi == 0) // bad rule set { throw new InvalidOperationException("The rule set " + name + " cannot format the value " + number); } NFRule result = rules[hi - 1]; // use shouldRollBack() to see whether we need to invoke the // rollback rule (see shouldRollBack()'s documentation for // an explanation of the rollback rule). If we do, roll back // one rule and return that one instead of the one we'd normally // return if (result.ShouldRollBack(number)) { if (hi == 1) // bad rule set { throw new InvalidOperationException("The rule set " + name + " cannot roll back from the rule '" + result + "'"); } result = rules[hi - 2]; } return(result); } // else use the master rule return(fractionRules[2]); }
/// <summary> /// Construct the subordinate data structures used by this object. This /// function is called by the RuleBasedNumberFormat constructor after all the /// rule sets have been created to actually parse the description and build /// rules from it. Since any rule set can refer to any other rule set, we /// have to have created all of them before we can create anything else. /// </summary> /// /// <param name="description">The textual description of this rule set</param> /// <param name="owner">The formatter that owns this rule set</param> public void ParseRules(String description, RuleBasedNumberFormat owner) { // start by creating a Vector whose elements are Strings containing // the descriptions of the rules (one rule per element). The rules // are separated by semicolons (there's no escape facility: ALL // semicolons are rule delimiters) ArrayList ruleDescriptions = new ArrayList(); int oldP = 0; int p = description.IndexOf(';'); while (oldP != -1) { if (p != -1) { ruleDescriptions.Add(description.Substring(oldP, (p) - (oldP))); oldP = p + 1; } else { if (oldP < description.Length) { ruleDescriptions.Add(description.Substring(oldP)); } oldP = p; } p = description.IndexOf(';', p + 1); } // now go back through and build a vector of the rules themselves // (the number of elements in the description list isn't necessarily // the number of rules-- some descriptions may expend into two rules) ArrayList tempRules = new ArrayList(); // we keep track of the rule before the one we're currently working // on solely to support >>> substitutions NFRule predecessor = null; for (int i = 0; i < ruleDescriptions.Count; i++) { // makeRules (a factory method on NFRule) will return either // a single rule or an array of rules. Either way, add them // to our rule vector Object temp = IBM.ICU.Text.NFRule.MakeRules( (String)ruleDescriptions[i], this, predecessor, owner); if (temp is NFRule) { tempRules.Add(temp); predecessor = (NFRule)temp; } else if (temp is NFRule[]) { NFRule[] rulesToAdd = (NFRule[])temp; for (int j = 0; j < rulesToAdd.Length; j++) { tempRules.Add(rulesToAdd[j]); predecessor = rulesToAdd[j]; } } } // now we can bag the description list ruleDescriptions = null; // for rules that didn't specify a base value, their base values // were initialized to 0. Make another pass through the list and // set all those rules' base values. We also remove any special // rules from the list and put them into their own member variables long defaultBaseValue = 0; // (this isn't a for loop because we might be deleting items from // the vector-- we want to make sure we only increment i when // we _didn't_ delete aything from the vector) int i_0 = 0; while (i_0 < tempRules.Count) { NFRule rule = (NFRule)tempRules[i_0]; switch ((int)rule.GetBaseValue()) { // if the rule's base value is 0, fill in a default // base value (this will be 1 plus the preceding // rule's base value for regular rule sets, and the // same as the preceding rule's base value in fraction // rule sets) case 0: rule.SetBaseValue(defaultBaseValue); if (!isFractionRuleSet) { ++defaultBaseValue; } ++i_0; break; // if it's the negative-number rule, copy it into its own // data member and delete it from the list case IBM.ICU.Text.NFRule.NEGATIVE_NUMBER_RULE: negativeNumberRule = rule; ILOG.J2CsMapping.Collections.Collections.RemoveAt(tempRules, i_0); break; // if it's the improper fraction rule, copy it into the // correct element of fractionRules case IBM.ICU.Text.NFRule.IMPROPER_FRACTION_RULE: fractionRules[0] = rule; ILOG.J2CsMapping.Collections.Collections.RemoveAt(tempRules, i_0); break; // if it's the proper fraction rule, copy it into the // correct element of fractionRules case IBM.ICU.Text.NFRule.PROPER_FRACTION_RULE: fractionRules[1] = rule; ILOG.J2CsMapping.Collections.Collections.RemoveAt(tempRules, i_0); break; // if it's the master rule, copy it into the // correct element of fractionRules case IBM.ICU.Text.NFRule.MASTER_RULE: fractionRules[2] = rule; ILOG.J2CsMapping.Collections.Collections.RemoveAt(tempRules, i_0); break; // if it's a regular rule that already knows its base value, // check to make sure the rules are in order, and update // the default base value for the next rule default: if (rule.GetBaseValue() < defaultBaseValue) { throw new ArgumentException( "Rules are not in order, base: " + rule.GetBaseValue() + " < " + defaultBaseValue); } defaultBaseValue = rule.GetBaseValue(); if (!isFractionRuleSet) { ++defaultBaseValue; } ++i_0; break; } } // finally, we can copy the rules from the vector into a // fixed-length array rules = new NFRule[tempRules.Count]; tempRules.CopyTo((Object[])rules); }