Perform a little cleanup to the RBNF code.
X-SVN-Rev: 36160
*******************************************************************************
*/
-package com.ibm.icu.text;
+package com.ibm.icu.impl.text;
import java.util.HashMap;
import java.util.Map;
import com.ibm.icu.impl.ICUDebug;
+import com.ibm.icu.text.*;
import com.ibm.icu.util.ULocale;
/**
*/
package com.ibm.icu.text;
-import java.lang.String;
+import java.text.FieldPosition;
import java.text.ParsePosition;
import com.ibm.icu.impl.PatternProps;
*/
private String ruleText = null;
+ /**
+ * The rule's plural format when defined. This is not a substitution
+ * because it only works on the current baseValue. It's normally not used
+ * due to the overhead.
+ */
+ private PluralFormat rulePatternFormat = null;
+
/**
* The rule's first substitution (the one with the lower offset
* into the rule text)
int p = description.indexOf(":");
if (p == -1) {
setBaseValue(0);
- } else {
+ }
+ else {
// copy the descriptor out into its own string and strip it,
// along with any trailing whitespace, out of the original
// description
// check first to see if the rule descriptor matches the token
// for one of the special rules. If it does, set the base
// value to the correct identifier value
- if (descriptor.equals("-x")) {
- setBaseValue(NEGATIVE_NUMBER_RULE);
- }
- else if (descriptor.equals("x.x")) {
- setBaseValue(IMPROPER_FRACTION_RULE);
- }
- else if (descriptor.equals("0.x")) {
+ if (descriptor.equals("0.x")) {
setBaseValue(PROPER_FRACTION_RULE);
}
- else if (descriptor.equals("x.0")) {
- setBaseValue(MASTER_RULE);
- }
else if (descriptor.charAt(0) >= '0' && descriptor.charAt(0) <= '9') {
// if the rule descriptor begins with a digit, it's a descriptor
// for a normal rule
long tempValue = 0;
+ int descriptorLength = descriptor.length();
char c = 0;
p = 0;
// into "tempValue", skip periods, commas, and spaces,
// stop on a slash or > sign (or at the end of the string),
// and throw an exception on any other character
- while (p < descriptor.length()) {
+ while (p < descriptorLength) {
c = descriptor.charAt(p);
if (c >= '0' && c <= '9') {
tempValue = tempValue * 10 + (c - '0');
if (c == '/') {
tempValue = 0;
++p;
- while (p < descriptor.length()) {
+ while (p < descriptorLength) {
c = descriptor.charAt(p);
if (c >= '0' && c <= '9') {
tempValue = tempValue * 10 + (c - '0');
// If we see another character before reaching the end of
// the descriptor, that's also a syntax error.
if (c == '>') {
- while (p < descriptor.length()) {
+ while (p < descriptorLength) {
c = descriptor.charAt(p);
if (c == '>' && exponent > 0) {
--exponent;
}
}
}
+ else if (descriptor.equals("-x")) {
+ setBaseValue(NEGATIVE_NUMBER_RULE);
+ }
+ else if (descriptor.equals("x.x")) {
+ setBaseValue(IMPROPER_FRACTION_RULE);
+ }
+ else if (descriptor.equals("x.0")) {
+ setBaseValue(MASTER_RULE);
+ }
}
// finally, if the rule body begins with an apostrophe, strip it off
* creates the substitutions, and removes the substitution tokens
* from the rule's rule text.
* @param owner The rule set containing this rule
- * @param predecessor The rule preseding this one in "owners" rule list
+ * @param predecessor The rule preceding this one in "owners" rule list
* @param ruleText The rule text
*/
private void extractSubstitutions(NFRuleSet owner,
String ruleText,
NFRule predecessor) {
this.ruleText = ruleText;
+ this.rulePatternFormat = null;
sub1 = extractSubstitution(owner, predecessor);
- sub2 = extractSubstitution(owner, predecessor);
+ if (sub1.isNullSubstitution()) {
+ // Small optimization. There is no need to create a redundant NullSubstitution.
+ sub2 = sub1;
+ }
+ else {
+ sub2 = extractSubstitution(owner, predecessor);
+ }
+ ruleText = this.ruleText;
+ if (ruleText.startsWith("$(") && ruleText.endsWith(")")) {
+ int endType = ruleText.indexOf(',');
+ if (endType < 0) {
+ throw new IllegalArgumentException("Rule \"" + ruleText + "\" does not have a defined type");
+ }
+ String type = this.ruleText.substring(2, endType);
+ PluralRules.PluralType pluralType;
+ if ("cardinal".equals(type)) {
+ pluralType = PluralRules.PluralType.CARDINAL;
+ }
+ else if ("ordinal".equals(type)) {
+ pluralType = PluralRules.PluralType.ORDINAL;
+ }
+ else {
+ throw new IllegalArgumentException(type + " is an unknown type");
+ }
+ rulePatternFormat = formatter.createPluralFormat(pluralType,
+ ruleText.substring(endType + 1, ruleText.length() - 1));
+ }
}
private static final String[] RULE_PREFIXES = new String[] {
else if (baseValue == MASTER_RULE) {
result.append("x.0: ");
}
-
- // for a normal rule, write out its base value, and if the radix is
- // something other than 10, write out the radix (with the preceding
- // slash, of course). Then calculate the expected exponent and if
- // if isn't the same as the actual exponent, write an appropriate
- // number of > signs. Finally, terminate the whole thing with
- // a colon.
else {
+ // for a normal rule, write out its base value, and if the radix is
+ // something other than 10, write out the radix (with the preceding
+ // slash, of course). Then calculate the expected exponent and if
+ // if isn't the same as the actual exponent, write an appropriate
+ // number of > signs. Finally, terminate the whole thing with
+ // a colon.
result.append(String.valueOf(baseValue));
if (radix != 10) {
result.append('/').append(radix);
// into the right places in toInsertInto (notice we do the
// substitutions in reverse order so that the offsets don't get
// messed up)
- toInsertInto.insert(pos, ruleText);
- sub2.doSubstitution(number, toInsertInto, pos);
- sub1.doSubstitution(number, toInsertInto, pos);
+ if (rulePatternFormat == null) {
+ toInsertInto.insert(pos, ruleText);
+ }
+ else {
+ toInsertInto.insert(pos, rulePatternFormat.format(baseValue == 0 ? number : number/baseValue));
+ }
+ if (!sub2.isNullSubstitution()) {
+ sub2.doSubstitution(number, toInsertInto, pos);
+ }
+ if (!sub1.isNullSubstitution()) {
+ sub1.doSubstitution(number, toInsertInto, pos);
+ }
}
/**
// [again, we have two copies of this routine that do the same thing
// so that we don't sacrifice precision in a long by casting it
// to a double]
- toInsertInto.insert(pos, ruleText);
- sub2.doSubstitution(number, toInsertInto, pos);
- sub1.doSubstitution(number, toInsertInto, pos);
+ if (rulePatternFormat == null) {
+ toInsertInto.insert(pos, ruleText);
+ }
+ else {
+ toInsertInto.insert(pos, rulePatternFormat.format(number));
+ }
+ if (!sub2.isNullSubstitution()) {
+ sub2.doSubstitution(number, toInsertInto, pos);
+ }
+ if (!sub1.isNullSubstitution()) {
+ sub1.doSubstitution(number, toInsertInto, pos);
+ }
}
/**
// the substitution, giving us a new partial parse result
pp.setIndex(0);
double partialResult = matchToDelimiter(workText, start, tempBaseValue,
- ruleText.substring(sub1.getPos(), sub2.getPos()), pp, sub1,
- upperBound).doubleValue();
+ ruleText.substring(sub1.getPos(), sub2.getPos()), rulePatternFormat,
+ pp, sub1, upperBound).doubleValue();
// if we got a successful match (or were trying to match a
// null substitution), pp is now pointing at the first unmatched
// substitution if there's a successful match, giving us
// a real result
partialResult = matchToDelimiter(workText2, 0, partialResult,
- ruleText.substring(sub2.getPos()), pp2, sub2,
+ ruleText.substring(sub2.getPos()), rulePatternFormat, pp2, sub2,
upperBound).doubleValue();
// if we got a successful match on this second
* Double.
*/
private Number matchToDelimiter(String text, int startPos, double baseVal,
- String delimiter, ParsePosition pp, NFSubstitution sub, double upperBound) {
+ String delimiter, PluralFormat pluralFormatDelimiter, ParsePosition pp, NFSubstitution sub, double upperBound) {
// if "delimiter" contains real (i.e., non-ignorable) text, search
// it for "delimiter" beginning at "start". If that succeeds, then
// use "sub"'s doParse() method to match the text before the
// element array: element 0 is the position of the match, and
// element 1 is the number of characters that matched
// "delimiter".
- int[] temp = findText(text, delimiter, startPos);
+ int[] temp = findText(text, delimiter, pluralFormatDelimiter, startPos);
int dPos = temp[0];
int dLen = temp[1];
// copy of "delimiter" in "text" and repeat the loop if
// we find it
tempPP.setIndex(0);
- temp = findText(text, delimiter, dPos + dLen);
+ temp = findText(text, delimiter, pluralFormatDelimiter, dPos + dLen);
dPos = temp[0];
dLen = temp[1];
}
return scanner.prefixLength(str, prefix);
}
- // go through all this grief if we're in lenient-parse mode
- // if (formatter.lenientParseEnabled()) {
- // // get the formatter's collator and use it to create two
- // // collation element iterators, one over the target string
- // // and another over the prefix (right now, we'll throw an
- // // exception if the collator we get back from the formatter
- // // isn't a RuleBasedCollator, because RuleBasedCollator defines
- // // the CollationElementIteratoer protocol. Hopefully, this
- // // will change someday.)
- // //
- // // Previous code was matching "fifty-" against " fifty" and leaving
- // // the number " fifty-7" to parse as 43 (50 - 7).
- // // Also it seems that if we consume the entire prefix, that's ok even
- // // if we've consumed the entire string, so I switched the logic to
- // // reflect this.
- // RuleBasedCollator collator = (RuleBasedCollator)formatter.getCollator();
- // CollationElementIterator strIter = collator.getCollationElementIterator(str);
- // CollationElementIterator prefixIter = collator.getCollationElementIterator(prefix);
-
- // // match collation elements between the strings
- // int oStr = strIter.next();
- // int oPrefix = prefixIter.next();
-
- // while (oPrefix != CollationElementIterator.NULLORDER) {
- // // skip over ignorable characters in the target string
- // while (CollationElementIterator.primaryOrder(oStr) == 0 && oStr !=
- // CollationElementIterator.NULLORDER) {
- // oStr = strIter.next();
- // }
-
- // // skip over ignorable characters in the prefix
- // while (CollationElementIterator.primaryOrder(oPrefix) == 0 && oPrefix !=
- // CollationElementIterator.NULLORDER) {
- // oPrefix = prefixIter.next();
- // }
-
- // // if skipping over ignorables brought to the end of
- // // the prefix, we DID match: drop out of the loop
- // if (oPrefix == CollationElementIterator.NULLORDER) {
- // break;
- // }
-
- // // if skipping over ignorables brought us to the end
- // // of the target string, we didn't match and return 0
- // if (oStr == CollationElementIterator.NULLORDER) {
- // return 0;
- // }
-
- // // match collation elements from the two strings
- // // (considering only primary differences). If we
- // // get a mismatch, dump out and return 0
- // if (CollationElementIterator.primaryOrder(oStr) != CollationElementIterator.
- // primaryOrder(oPrefix)) {
- // return 0;
- // }
- // // otherwise, advance to the next character in each string
- // // and loop (we drop out of the loop when we exhaust
- // // collation elements in the prefix)
-
- // oStr = strIter.next();
- // oPrefix = prefixIter.next();
- // }
-
- // // we are not compatible with jdk 1.1 any longer
- // int result = strIter.getOffset();
- // if (oStr != CollationElementIterator.NULLORDER) {
- // --result;
- // }
- // return result;
-
- /*
- //----------------------------------------------------------------
- // JDK 1.2-specific API call
- // return strIter.getOffset();
- //----------------------------------------------------------------
- // JDK 1.1 HACK (take out for 1.2-specific code)
-
- // if we make it to here, we have a successful match. Now we
- // have to find out HOW MANY characters from the target string
- // matched the prefix (there isn't necessarily a one-to-one
- // mapping between collation elements and characters).
- // In JDK 1.2, there's a simple getOffset() call we can use.
- // In JDK 1.1, on the other hand, we have to go through some
- // ugly contortions. First, use the collator to compare the
- // same number of characters from the prefix and target string.
- // If they're equal, we're done.
- collator.setStrength(Collator.PRIMARY);
- if (str.length() >= prefix.length()
- && collator.equals(str.substring(0, prefix.length()), prefix)) {
- return prefix.length();
- }
-
- // if they're not equal, then we have to compare successively
- // larger and larger substrings of the target string until we
- // get to one that matches the prefix. At that point, we know
- // how many characters matched the prefix, and we can return.
- int p = 1;
- while (p <= str.length()) {
- if (collator.equals(str.substring(0, p), prefix)) {
- return p;
- } else {
- ++p;
- }
- }
-
- // SHOULKD NEVER GET HERE!!!
- return 0;
- //----------------------------------------------------------------
- */
-
- // If lenient parsing is turned off, forget all that crap above.
- // Just use String.startsWith() and be done with it.
- // } else {
- if (str.startsWith(prefix)) {
- return prefix.length();
- } else {
- return 0;
- }
- // }
+ // If lenient parsing is turned off, forget all that crap above.
+ // Just use String.startsWith() and be done with it.
+ if (str.startsWith(prefix)) {
+ return prefix.length();
+ }
+ return 0;
}
- /*
- * Searches a string for another string. If lenient parsing is off,
- * this just calls indexOf(). If lenient parsing is on, this function
- * uses CollationElementIterator to match characters, and only
- * primary-order differences are significant in determining whether
- * there's a match.
- * @param str The string to search
- * @param key The string to search "str" for
- * @return A two-element array of ints. Element 0 is the position
- * of the match, or -1 if there was no match. Element 1 is the
- * number of characters in "str" that matched (which isn't necessarily
- * the same as the length of "key")
- */
-/* private int[] findText(String str, String key) {
- return findText(str, key, 0);
- }*/
-
/**
* Searches a string for another string. If lenient parsing is off,
* this just calls indexOf(). If lenient parsing is on, this function
* number of characters in "str" that matched (which isn't necessarily
* the same as the length of "key")
*/
- private int[] findText(String str, String key, int startingAt) {
- // if lenient parsing is turned off, this is easy: just call
- // String.indexOf() and we're done
+ private int[] findText(String str, String key, PluralFormat pluralFormatKey, int startingAt) {
RbnfLenientScanner scanner = formatter.getLenientScanner();
-// if (!formatter.lenientParseEnabled()) {
- if (scanner == null) {
- return new int[] { str.indexOf(key, startingAt), key.length() };
+ if (pluralFormatKey != null) {
+ FieldPosition position = new FieldPosition(NumberFormat.INTEGER_FIELD);
+ position.setBeginIndex(startingAt);
+ pluralFormatKey.parseType(str, scanner, position);
+ int start = position.getBeginIndex();
+ if (start >= 0) {
+ return new int[]{start, position.getEndIndex() - start};
+ }
+ return new int[]{-1, 0};
+ }
- // but if lenient parsing is turned ON, we've got some work
+ if (scanner != null) {
+ // if lenient parsing is turned ON, we've got some work
// ahead of us
- } else {
return scanner.findText(str, key, startingAt);
-
- // //----------------------------------------------------------------
- // // JDK 1.1 HACK (take out of 1.2-specific code)
-
- // // in JDK 1.2, CollationElementIterator provides us with an
- // // API to map between character offsets and collation elements
- // // and we can do this by marching through the string comparing
- // // collation elements. We can't do that in JDK 1.1. Insted,
- // // we have to go through this horrible slow mess:
- // int p = startingAt;
- // int keyLen = 0;
-
- // // basically just isolate smaller and smaller substrings of
- // // the target string (each running to the end of the string,
- // // and with the first one running from startingAt to the end)
- // // and then use prefixLength() to see if the search key is at
- // // the beginning of each substring. This is excruciatingly
- // // slow, but it will locate the key and tell use how long the
- // // matching text was.
- // while (p < str.length() && keyLen == 0) {
- // keyLen = prefixLength(str.substring(p), key);
- // if (keyLen != 0) {
- // return new int[] { p, keyLen };
- // }
- // ++p;
- // }
- // // if we make it to here, we didn't find it. Return -1 for the
- // // location. The length should be ignored, but set it to 0,
- // // which should be "safe"
- // return new int[] { -1, 0 };
-
- //----------------------------------------------------------------
- // JDK 1.2 version of this routine
- //RuleBasedCollator collator = (RuleBasedCollator)formatter.getCollator();
- //
- //CollationElementIterator strIter = collator.getCollationElementIterator(str);
- //CollationElementIterator keyIter = collator.getCollationElementIterator(key);
- //
- //int keyStart = -1;
- //
- //str.setOffset(startingAt);
- //
- //int oStr = strIter.next();
- //int oKey = keyIter.next();
- //while (oKey != CollationElementIterator.NULLORDER) {
- // while (oStr != CollationElementIterator.NULLORDER &&
- // CollationElementIterator.primaryOrder(oStr) == 0)
- // oStr = strIter.next();
- //
- // while (oKey != CollationElementIterator.NULLORDER &&
- // CollationElementIterator.primaryOrder(oKey) == 0)
- // oKey = keyIter.next();
- //
- // if (oStr == CollationElementIterator.NULLORDER) {
- // return new int[] { -1, 0 };
- // }
- //
- // if (oKey == CollationElementIterator.NULLORDER) {
- // break;
- // }
- //
- // if (CollationElementIterator.primaryOrder(oStr) ==
- // CollationElementIterator.primaryOrder(oKey)) {
- // keyStart = strIter.getOffset();
- // oStr = strIter.next();
- // oKey = keyIter.next();
- // } else {
- // if (keyStart != -1) {
- // keyStart = -1;
- // keyIter.reset();
- // } else {
- // oStr = strIter.next();
- // }
- // }
- //}
- //
- //if (oKey == CollationElementIterator.NULLORDER) {
- // return new int[] { keyStart, strIter.getOffset() - keyStart };
- //} else {
- // return new int[] { -1, 0 };
- //}
}
+ // if lenient parsing is turned off, this is easy. Just call
+ // String.indexOf() and we're done
+ return new int[]{str.indexOf(key, startingAt), key.length()};
}
/**
return true;
}
RbnfLenientScanner scanner = formatter.getLenientScanner();
- if (scanner != null) {
- return scanner.allIgnorable(str);
- }
- return false;
-
- // if lenient parsing is turned on, walk through the string with
- // a collation element iterator and make sure each collation
- // element is 0 (ignorable) at the primary level
- // if (formatter.lenientParseEnabled()) {
- // {dlf}
- //return false;
- // RuleBasedCollator collator = (RuleBasedCollator)(formatter.getCollator());
- // CollationElementIterator iter = collator.getCollationElementIterator(str);
-
- // int o = iter.next();
- // while (o != CollationElementIterator.NULLORDER
- // && CollationElementIterator.primaryOrder(o) == 0) {
- // o = iter.next();
- // }
- // return o == CollationElementIterator.NULLORDER;
-
- // if lenient parsing is turned off, there is no such thing as
- // an ignorable character: return true only if the string is empty
- // } else {
- // return false;
- // }
+ return scanner != null && scanner.allIgnorable(str);
}
}
}
switch (description.charAt(0)) {
- // if the description begins with '<'...
case '<':
- // throw an exception if the rule is a negative number
- // rule
- ///CLOVER:OFF
- // If you look at the call hierarchy of this method, the rule would
- // never be directly modified by the user and therefore makes the
- // following pointless unless the user changes the ruleset.
if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) {
+ // throw an exception if the rule is a negative number rule
+ ///CLOVER:OFF
+ // If you look at the call hierarchy of this method, the rule would
+ // never be directly modified by the user and therefore makes the
+ // following pointless unless the user changes the ruleset.
throw new IllegalArgumentException("<< not allowed in negative-number rule");
+ ///CLOVER:ON
}
- ///CLOVER:ON
-
- // if the rule is a fraction rule, return an
- // IntegralPartSubstitution
else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
- || rule.getBaseValue() == NFRule.MASTER_RULE) {
+ || rule.getBaseValue() == NFRule.MASTER_RULE)
+ {
+ // if the rule is a fraction rule, return an IntegralPartSubstitution
return new IntegralPartSubstitution(pos, ruleSet, formatter, description);
}
-
- // if the rule set containing the rule is a fraction
- // rule set, return a NumeratorSubstitution
else if (ruleSet.isFractionSet()) {
+ // if the rule set containing the rule is a fraction
+ // rule set, return a NumeratorSubstitution
return new NumeratorSubstitution(pos, rule.getBaseValue(),
formatter.getDefaultRuleSet(), formatter, description);
}
-
- // otherwise, return a MultiplierSubstitution
else {
+ // otherwise, return a MultiplierSubstitution
return new MultiplierSubstitution(pos, rule.getDivisor(), ruleSet,
formatter, description);
}
- // if the description begins with '>'...
case '>':
- // if the rule is a negative-number rule, return
- // an AbsoluteValueSubstitution
if (rule.getBaseValue() == NFRule.NEGATIVE_NUMBER_RULE) {
+ // if the rule is a negative-number rule, return
+ // an AbsoluteValueSubstitution
return new AbsoluteValueSubstitution(pos, ruleSet, formatter, description);
}
-
- // if the rule is a fraction rule, return a
- // FractionalPartSubstitution
else if (rule.getBaseValue() == NFRule.IMPROPER_FRACTION_RULE
|| rule.getBaseValue() == NFRule.PROPER_FRACTION_RULE
- || rule.getBaseValue() == NFRule.MASTER_RULE) {
+ || rule.getBaseValue() == NFRule.MASTER_RULE)
+ {
+ // if the rule is a fraction rule, return a
+ // FractionalPartSubstitution
return new FractionalPartSubstitution(pos, ruleSet, formatter, description);
}
-
- // if the rule set owning the rule is a fraction rule set,
- // throw an exception
- ///CLOVER:OFF
- // If you look at the call hierarchy of this method, the rule would
- // never be directly modified by the user and therefore makes the
- // following pointless unless the user changes the ruleset.
else if (ruleSet.isFractionSet()) {
+ // if the rule set owning the rule is a fraction rule set,
+ // throw an exception
+ ///CLOVER:OFF
+ // If you look at the call hierarchy of this method, the rule would
+ // never be directly modified by the user and therefore makes the
+ // following pointless unless the user changes the ruleset.
throw new IllegalArgumentException(">> not allowed in fraction rule set");
+ ///CLOVER:ON
}
- ///CLOVER:ON
-
- // otherwise, return a ModulusSubstitution
else {
+ // otherwise, return a ModulusSubstitution
return new ModulusSubstitution(pos, rule.getDivisor(), rulePredecessor,
ruleSet, formatter, description);
}
-
- // if the description begins with '=', always return a
- // SameValueSubstitution
case '=':
return new SameValueSubstitution(pos, ruleSet, formatter, description);
-
+ default:
// and if it's anything else, throw an exception
///CLOVER:OFF
// If you look at the call hierarchy of this method, the rule would
// never be directly modified by the user and therefore makes the
// following pointless unless the user changes the ruleset.
- default:
throw new IllegalArgumentException("Illegal substitution character");
///CLOVER:ON
}
// If it doesn't that's a syntax error. Otherwise,
// makeSubstitution() was the only thing that needed to know
// about these characters, so strip them off
- if (description.length() >= 2 && description.charAt(0) == description.charAt(
- description.length() - 1)) {
+ if (description.length() >= 2 && description.charAt(0) == description.charAt(description.length() - 1)) {
description = description.substring(1, description.length() - 1);
}
else if (description.length() != 0) {
// rule, we keep a copy of the divisor
this.divisor = divisor;
- if (divisor == 0) { // this will cause recursion
- throw new IllegalStateException("Substitution with bad divisor (" + divisor + ") " + description.substring(0, pos) +
- " | " + description.substring(pos));
- }
+ if (divisor == 0) { // this will cause recursion
+ throw new IllegalStateException("Substitution with divisor 0 " + description.substring(0, pos) +
+ " | " + description.substring(pos));
+ }
}
/**
public void setDivisor(int radix, int exponent) {
divisor = Math.pow(radix, exponent);
- if (divisor == 0) {
- throw new IllegalStateException("Substitution with divisor 0");
- }
+ if (divisor == 0) {
+ throw new IllegalStateException("Substitution with divisor 0");
+ }
}
//-----------------------------------------------------------------------
}
}
- public int hashCode() {
- assert false : "hashCode not designed";
- return 42;
- }
-
//-----------------------------------------------------------------------
// formatting
//-----------------------------------------------------------------------
}
}
- public int hashCode() {
- assert false : "hashCode not designed";
- return 42;
- }
-
//-----------------------------------------------------------------------
// formatting
//-----------------------------------------------------------------------
*/
private boolean useSpaces = true;
- /*
- * The largest number of digits after the decimal point that this
- * object will show in "by digits" mode
- */
- //private static final int MAXDECIMALDIGITS = 18; // 8
-
//-----------------------------------------------------------------------
// construction
//-----------------------------------------------------------------------
RuleBasedNumberFormat formatter,
String description) {
super(pos, ruleSet, formatter, description);
-// boolean chevron = description.startsWith(">>") || ruleSet == this.ruleSet;
-// if (chevron || ruleSet == this.ruleSet) {
-
if (description.equals(">>") || description.equals(">>>") || ruleSet == this.ruleSet) {
byDigits = true;
if (description.equals(">>>")) {
* toInsertInto
*/
public void doSubstitution(double number, StringBuffer toInsertInto, int position) {
- // if we're not in "byDigits" mode, just use the inherited
- // doSubstitution() routine
if (!byDigits) {
+ // if we're not in "byDigits" mode, just use the inherited
+ // doSubstitution() routine
super.doSubstitution(number, toInsertInto, position);
-
- // if we're in "byDigits" mode, transform the value into an integer
- // by moving the decimal point eight places to the right and
- // pulling digits off the right one at a time, formatting each digit
- // as an integer using this substitution's owning rule set
- // (this is slower, but more accurate, than doing it from the
- // other end)
- } else {
-// int numberToFormat = (int)Math.round(transformNumber(number) * Math.pow(
-// 10, MAXDECIMALDIGITS));
-// long numberToFormat = (long)Math.round(transformNumber(number) * Math.pow(10, MAXDECIMALDIGITS));
+ }
+ else {
+ // if we're in "byDigits" mode, transform the value into an integer
+ // by moving the decimal point eight places to the right and
+ // pulling digits off the right one at a time, formatting each digit
+ // as an integer using this substitution's owning rule set
+ // (this is slower, but more accurate, than doing it from the
+ // other end)
// just print to string and then use that
DigitList dl = new DigitList();
dl.set(number, 20, true);
- // this flag keeps us from formatting trailing zeros. It starts
- // out false because we're pulling from the right, and switches
- // to true the first time we encounter a non-zero digit
-// boolean doZeros = false;
-// System.out.println("class: " + getClass().getName());
-// System.out.println("number: " + number + " transformed: " + transformNumber(number));
-// System.out.println("formatting " + numberToFormat);
-// for (int i = 0; i < MAXDECIMALDIGITS; i++) {
-// int digit = (int)(numberToFormat % 10);
-// System.out.println(" #: '" + numberToFormat + "'" + " digit '" + digit + "'");
-// if (digit != 0 || doZeros) {
-// if (doZeros && useSpaces) {
-// toInsertInto.insert(pos + this.pos, ' ');
-// }
-// doZeros = true;
-// ruleSet.format(digit, toInsertInto, pos + this.pos);
-// }
-// numberToFormat /= 10;
-// }
-
boolean pad = false;
while (dl.count > Math.max(0, dl.decimalAt)) {
if (pad && useSpaces) {
ParsePosition workPos = new ParsePosition(1);
double result = 0;
int digit;
-// double p10 = 0.1;
-
-// while (workText.length() > 0 && workPos.getIndex() != 0) {
-// workPos.setIndex(0);
-// digit = ruleSet.parse(workText, workPos, 10).intValue();
-// if (lenientParse && workPos.getIndex() == 0) {
-// digit = NumberFormat.getInstance().parse(workText, workPos).intValue();
-// }
-
-// if (workPos.getIndex() != 0) {
-// result += digit * p10;
-// p10 /= 10;
-// parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex());
-// workText = workText.substring(workPos.getIndex());
-// while (workText.length() > 0 && workText.charAt(0) == ' ') {
-// workText = workText.substring(1);
-// parsePosition.setIndex(parsePosition.getIndex() + 1);
-// }
-// }
-// }
-
DigitList dl = new DigitList();
while (workText.length() > 0 && workPos.getIndex() != 0) {
}
}
- public int hashCode() {
- assert false : "hashCode not designed";
- return 42;
- }
-
//-----------------------------------------------------------------------
// formatting
//-----------------------------------------------------------------------
return super.equals(that);
}
- public int hashCode() {
- assert false : "hashCode not designed";
- return 42;
- }
-
/**
* NullSubstitutions don't show up in the textual representation
* of a RuleBasedNumberFormat
* @stable ICU 3.8
*/
public PluralFormat() {
- init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
+ init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
}
/**
* @stable ICU 3.8
*/
public PluralFormat(ULocale ulocale) {
- init(null, PluralType.CARDINAL, ulocale);
+ init(null, PluralType.CARDINAL, ulocale, null);
}
/**
* @stable ICU 3.8
*/
public PluralFormat(PluralRules rules) {
- init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
+ init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
}
/**
* @stable ICU 3.8
*/
public PluralFormat(ULocale ulocale, PluralRules rules) {
- init(rules, PluralType.CARDINAL, ulocale);
+ init(rules, PluralType.CARDINAL, ulocale, null);
}
/**
* @stable ICU 50
*/
public PluralFormat(ULocale ulocale, PluralType type) {
- init(null, type, ulocale);
+ init(null, type, ulocale, null);
}
/**
* @stable ICU 3.8
*/
public PluralFormat(String pattern) {
- init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
+ init(null, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
applyPattern(pattern);
}
* @stable ICU 3.8
*/
public PluralFormat(ULocale ulocale, String pattern) {
- init(null, PluralType.CARDINAL, ulocale);
+ init(null, PluralType.CARDINAL, ulocale, null);
applyPattern(pattern);
}
* @stable ICU 3.8
*/
public PluralFormat(PluralRules rules, String pattern) {
- init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT));
+ init(rules, PluralType.CARDINAL, ULocale.getDefault(Category.FORMAT), null);
applyPattern(pattern);
}
* @stable ICU 3.8
*/
public PluralFormat(ULocale ulocale, PluralRules rules, String pattern) {
- init(rules, PluralType.CARDINAL, ulocale);
+ init(rules, PluralType.CARDINAL, ulocale, null);
applyPattern(pattern);
}
* @stable ICU 50
*/
public PluralFormat(ULocale ulocale, PluralType type, String pattern) {
- init(null, type, ulocale);
+ init(null, type, ulocale, null);
+ applyPattern(pattern);
+ }
+
+ /**
+ * Creates a new <code>PluralFormat</code> for a plural type, a
+ * pattern and a locale.
+ * @param ulocale the <code>PluralFormat</code> will be configured with
+ * rules for this locale. This locale will also be used for standard
+ * number formatting.
+ * @param type The plural type (e.g., cardinal or ordinal).
+ * @param pattern the pattern for this <code>PluralFormat</code>.
+ * @param numberFormat The number formatter to use.
+ * @throws IllegalArgumentException if the pattern is invalid.
+ * @stable ICU 50
+ */
+ /*package*/ PluralFormat(ULocale ulocale, PluralType type, String pattern, NumberFormat numberFormat) {
+ init(null, type, ulocale, numberFormat);
applyPattern(pattern);
}
* <code>numberFormat</code>: a <code>NumberFormat</code> for the locale
* <code>ulocale</code>.
*/
- private void init(PluralRules rules, PluralType type, ULocale locale) {
+ private void init(PluralRules rules, PluralType type, ULocale locale, NumberFormat numberFormat) {
ulocale = locale;
pluralRules = (rules == null) ? PluralRules.forLocale(ulocale, type)
: rules;
resetPattern();
- numberFormat = NumberFormat.getInstance(ulocale);
+ this.numberFormat = (numberFormat == null) ? NumberFormat.getInstance(ulocale) : numberFormat;
}
private void resetPattern() {
return toAppendTo;
}
- private final String format(Number numberObject, double number) {
+ private String format(Number numberObject, double number) {
// If no pattern was applied, return the formatted number.
if (msgPattern == null || msgPattern.countParts() == 0) {
return numberFormat.format(numberObject);
* @stable ICU 3.8
*/
public Number parse(String text, ParsePosition parsePosition) {
+ // You get number ranges from this. You can't get an exact number.
throw new UnsupportedOperationException();
}
throw new UnsupportedOperationException();
}
+ /**
+ * This method returns the PluralRules type found from parsing.
+ * @param source the string to be parsed.
+ * @param pos defines the position where parsing is to begin,
+ * and upon return, the position where parsing left off. If the position
+ * is a negative index, then parsing failed.
+ * @return Returns the PluralRules type. For example, it could be "zero", "one", "two", "few", "many" or "other")
+ */
+ /*package*/ String parseType(String source, RbnfLenientScanner scanner, FieldPosition pos) {
+ // If no pattern was applied, return null.
+ if (msgPattern == null || msgPattern.countParts() == 0) {
+ pos.setBeginIndex(-1);
+ pos.setEndIndex(-1);
+ return null;
+ }
+ int partIndex = 0;
+ int currMatchIndex;
+ int count=msgPattern.countParts();
+ int startingAt = pos.getBeginIndex();
+ if (startingAt < 0) {
+ startingAt = 0;
+ }
+
+ // The keyword is null until we need to match against a non-explicit, not-"other" value.
+ // Then we get the keyword from the selector.
+ // (In other words, we never call the selector if we match against an explicit value,
+ // or if the only non-explicit keyword is "other".)
+ String keyword = null;
+ String matchedWord = null;
+ int matchedIndex = -1;
+ // Iterate over (ARG_SELECTOR ARG_START message ARG_LIMIT) tuples
+ // until the end of the plural-only pattern.
+ do {
+ MessagePattern.Part partSelector=msgPattern.getPart(partIndex++);
+ if (partSelector.getType() != MessagePattern.Part.Type.ARG_SELECTOR) {
+ // Bad format
+ continue;
+ }
+
+ MessagePattern.Part partStart=msgPattern.getPart(partIndex++);
+ if (partStart.getType() != MessagePattern.Part.Type.MSG_START) {
+ // Bad format
+ continue;
+ }
+
+ MessagePattern.Part partLimit=msgPattern.getPart(partIndex++);
+ if (partLimit.getType() != MessagePattern.Part.Type.MSG_LIMIT) {
+ // Bad format
+ continue;
+ }
+
+ String currArg = pattern.substring(partStart.getLimit(), partLimit.getIndex());
+ if (scanner != null) {
+ // If lenient parsing is turned ON, we've got some time consuming parsing ahead of us.
+ int[] scannerMatchResult = scanner.findText(source, currArg, startingAt);
+ currMatchIndex = scannerMatchResult[0];
+ }
+ else {
+ currMatchIndex = source.indexOf(currArg);
+ }
+ if (currMatchIndex > matchedIndex && (matchedWord == null || currArg.length() > matchedWord.length())) {
+ matchedIndex = currMatchIndex;
+ matchedWord = currArg;
+ keyword = pattern.substring(partStart.getLimit(), partLimit.getIndex());
+ }
+ } while(partIndex<count);
+ if (keyword != null) {
+ pos.setBeginIndex(matchedIndex);
+ pos.setEndIndex(matchedIndex + matchedWord.length());
+ return keyword;
+ }
+
+ // Not found!
+ pos.setBeginIndex(-1);
+ pos.setEndIndex(-1);
+ return null;
+ }
+
/**
* Sets the locale used by this <code>PluraFormat</code> object.
* Note: Calling this method resets this <code>PluraFormat</code> object,
if (ulocale == null) {
ulocale = ULocale.getDefault(Category.FORMAT);
}
- init(null, PluralType.CARDINAL, ulocale);
+ init(null, PluralType.CARDINAL, ulocale, null);
}
/**
// the same time, but you get what you get, and you shouldn't be using this from
// multiple threads anyway.
if (scannerProvider == null && lenientParse && !lookedForScanner) {
- ///CLOVER:OFF
try {
lookedForScanner = true;
- Class<?> cls = Class.forName("com.ibm.icu.text.RbnfScannerProviderImpl");
+ Class<?> cls = Class.forName("com.ibm.icu.impl.text.RbnfScannerProviderImpl");
RbnfLenientScannerProvider provider = (RbnfLenientScannerProvider)cls.newInstance();
setLenientScannerProvider(provider);
}
catch (Exception e) {
// any failure, we just ignore and return null
}
- ///CLOVER:ON
}
return scannerProvider;
DecimalFormat getDecimalFormat() {
if (decimalFormat == null) {
decimalFormat = (DecimalFormat)NumberFormat.getInstance(locale);
-
+
if (decimalFormatSymbols != null) {
decimalFormat.setDecimalFormatSymbols(decimalFormatSymbols);
}
return decimalFormat;
}
+ PluralFormat createPluralFormat(PluralRules.PluralType pluralType, String pattern) {
+ return new PluralFormat(locale, pluralType, pattern, getDecimalFormat());
+ }
+
//-----------------------------------------------------------------------
// construction implementation
//-----------------------------------------------------------------------
/*
*******************************************************************************
- * Copyright (C) 2009-2013, International Business Machines Corporation and *
+ * Copyright (C) 2009-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.text.RbnfLenientScannerProvider;
-import com.ibm.icu.text.RbnfScannerProviderImpl;
+import com.ibm.icu.impl.text.RbnfScannerProviderImpl;
import com.ibm.icu.text.RuleBasedNumberFormat;
import com.ibm.icu.util.ULocale;
" 9: <0</9;\n" +
" 10: <0</10;\n";
- static {
- // mondo hack
- char[] fracRulesArr = fracRules.toCharArray();
- int len = fracRulesArr.length;
- int change = 2;
- for (int i = 0; i < len; ++i) {
- char ch = fracRulesArr[i];
- if (ch == '\n') {
- change = 2; // change ok
- } else if (ch == ':') {
- change = 1; // change, but once we hit a non-space char, don't change
- } else if (ch == ' ') {
- if (change != 0) {
- fracRulesArr[i] = (char)0x200e;
- }
- } else {
- if (change == 1) {
- change = 0;
- }
- }
- }
- fracRules = new String(fracRulesArr);
- }
-
- static final String durationInSecondsRules =
- // main rule set for formatting with words
- "%with-words:\n"
- // take care of singular and plural forms of "second"
- + " 0 seconds; 1 second; =0= seconds;\n"
- // use %%min to format values greater than 60 seconds
- + " 60/60: <%%min<[, >>];\n"
- // use %%hr to format values greater than 3,600 seconds
- // (the ">>>" below causes us to see the number of minutes
- // when when there are zero minutes)
- + " 3600/60: <%%hr<[, >>>];\n"
- // this rule set takes care of the singular and plural forms
- // of "minute"
- + "%%min:\n"
- + " 0 minutes; 1 minute; =0= minutes;\n"
- // this rule set takes care of the singular and plural forms
- // of "hour"
- + "%%hr:\n"
- + " 0 hours; 1 hour; =0= hours;\n"
-
- // main rule set for formatting in numerals
- + "%in-numerals:\n"
- // values below 60 seconds are shown with "sec."
- + " =0= sec.;\n"
- // higher values are shown with colons: %%min-sec is used for
- // values below 3,600 seconds...
- + " 60: =%%min-sec=;\n"
- // ...and %%hr-min-sec is used for values of 3,600 seconds
- // and above
- + " 3600: =%%hr-min-sec=;\n"
- // this rule causes values of less than 10 minutes to show without
- // a leading zero
- + "%%min-sec:\n"
- + " 0: :=00=;\n"
- + " 60/60: <0<>>;\n"
- // this rule set is used for values of 3,600 or more. Minutes are always
- // shown, and always shown with two digits
- + "%%hr-min-sec:\n"
- + " 0: :=00=;\n"
- + " 60/60: <00<>>;\n"
- + " 3600/60: <#,##0<:>>>;\n"
- // the lenient-parse rules allow several different characters to be used
- // as delimiters between hours, minutes, and seconds
- + "%%lenient-parse:\n"
- + " & : = . = ' ' = -;\n";
-
public void TestCoverage() {
+ String durationInSecondsRules =
+ // main rule set for formatting with words
+ "%with-words:\n"
+ // take care of singular and plural forms of "second"
+ + " 0 seconds; 1 second; =0= seconds;\n"
+ // use %%min to format values greater than 60 seconds
+ + " 60/60: <%%min<[, >>];\n"
+ // use %%hr to format values greater than 3,600 seconds
+ // (the ">>>" below causes us to see the number of minutes
+ // when when there are zero minutes)
+ + " 3600/60: <%%hr<[, >>>];\n"
+ // this rule set takes care of the singular and plural forms
+ // of "minute"
+ + "%%min:\n"
+ + " 0 minutes; 1 minute; =0= minutes;\n"
+ // this rule set takes care of the singular and plural forms
+ // of "hour"
+ + "%%hr:\n"
+ + " 0 hours; 1 hour; =0= hours;\n"
+
+ // main rule set for formatting in numerals
+ + "%in-numerals:\n"
+ // values below 60 seconds are shown with "sec."
+ + " =0= sec.;\n"
+ // higher values are shown with colons: %%min-sec is used for
+ // values below 3,600 seconds...
+ + " 60: =%%min-sec=;\n"
+ // ...and %%hr-min-sec is used for values of 3,600 seconds
+ // and above
+ + " 3600: =%%hr-min-sec=;\n"
+ // this rule causes values of less than 10 minutes to show without
+ // a leading zero
+ + "%%min-sec:\n"
+ + " 0: :=00=;\n"
+ + " 60/60: <0<>>;\n"
+ // this rule set is used for values of 3,600 or more. Minutes are always
+ // shown, and always shown with two digits
+ + "%%hr-min-sec:\n"
+ + " 0: :=00=;\n"
+ + " 60/60: <00<>>;\n"
+ + " 3600/60: <#,##0<:>>>;\n"
+ // the lenient-parse rules allow several different characters to be used
+ // as delimiters between hours, minutes, and seconds
+ + "%%lenient-parse:\n"
+ + " & : = . = ' ' = -;\n";
+
// extra calls to boost coverage numbers
RuleBasedNumberFormat fmt0 = new RuleBasedNumberFormat(RuleBasedNumberFormat.SPELLOUT);
RuleBasedNumberFormat fmt1 = (RuleBasedNumberFormat)fmt0.clone();
doTest(formatter, testData, true);
}
-// /**
-// * Perform a simple spot check on the ordinal spellout rules
-// */
-// public void TestOrdinalSpellout() {
-// String rules = "%%digits-ordinal-indicator:"
-// + "0=1: th;"
-// + "1=1: st;"
-// + "2=1: nd;"
-// + "3=1: rd;"
-// + "4=1: th;"
-// + "20=1: >>;"
-// + "100=1: >>;"
-// + "%digits-ordinal:"
-// + "-x: −>>;"
-// + "0: =#,##0==%%digits-ordinal-indicator=;";
-// RuleBasedNumberFormat formatter = new RuleBasedNumberFormat(rules);
-// String[][] testData = {
-// { "1", "1st" },
-// { "2", "2nd" },
-// { "3", "3rd" },
-// { "4", "4th" },
-// { "11", "11th" },
-// { "12", "12th" },
-// { "13", "13th" },
-// { "14", "14th" },
-// { "21", "21st" },
-// { "22", "22nd" },
-// { "23", "23rd" },
-// { "24", "24th" },
-// };
-//
-// doTest(formatter, testData, true);
-// }
+ /**
+ * Perform a simple spot check on the ordinal spellout rules
+ */
+ public void TestPluralRules() {
+ String enRules = "%digits-ordinal:"
+ + "-x: −>>;"
+ + "0: =#,##0=$(ordinal,one{st}two{nd}few{rd}other{th});";
+ RuleBasedNumberFormat enFormatter = new RuleBasedNumberFormat(enRules, ULocale.ENGLISH);
+ String[][] enTestData = {
+ { "1", "1st" },
+ { "2", "2nd" },
+ { "3", "3rd" },
+ { "4", "4th" },
+ { "11", "11th" },
+ { "12", "12th" },
+ { "13", "13th" },
+ { "14", "14th" },
+ { "21", "21st" },
+ { "22", "22nd" },
+ { "23", "23rd" },
+ { "24", "24th" },
+ };
+
+ doTest(enFormatter, enTestData, true);
+
+ // This is trying to model the feminine form, but don't worry about the details too much.
+ // We're trying to test the plural rules.
+ String ruRules = "%spellout-numbering:"
+ + "-x: минус >>;"
+ + "x.x: << запятая >>;"
+ + "0: ноль;"
+ + "1: один;"
+ + "2: два;"
+ + "3: три;"
+ + "4: четыре;"
+ + "5: пять;"
+ + "6: шесть;"
+ + "7: семь;"
+ + "8: восемь;"
+ + "9: девять;"
+ + "10: десять;"
+ + "11: одиннадцать;"
+ + "12: двенадцать;"
+ + "13: тринадцать;"
+ + "14: четырнадцать;"
+ + "15: пятнадцать;"
+ + "16: шестнадцать;"
+ + "17: семнадцать;"
+ + "18: восемнадцать;"
+ + "19: девятнадцать;"
+ + "20: двадцать[ >>];"
+ + "30: тридцать[ >>];"
+ + "40: сорок[ >>];"
+ + "50: пятьдесят[ >>];"
+ + "60: шестьдесят[ >>];"
+ + "70: семьдесят[ >>];"
+ + "80: восемьдесят[ >>];"
+ + "90: девяносто[ >>];"
+ + "100: сто[ >>];"
+ + "200: <<сти[ >>];"
+ + "300: <<ста[ >>];"
+ + "500: <<сот[ >>];"
+ + "1000: <<$(cardinal,one{ тысяча}few{ тысячи}other{ тысяч})[ >>];";
+ RuleBasedNumberFormat ruFormatter = new RuleBasedNumberFormat(ruRules, new ULocale("ru"));
+ String[][] ruTestData = {
+ { "1", "один" },
+ { "100", "сто" },
+ { "125", "сто двадцать пять" },
+ { "399", "триста девяносто девять" },
+ { "1,000", "один тысяча" },
+ { "2,000", "два тысячи" },
+ { "5,000", "пять тысяч" },
+ { "21,000", "двадцать один тысяча" },
+ { "22,000", "двадцать два тысячи" },
+ };
+
+ doTest(ruFormatter, ruTestData, true);
+
+ // Make sure there are no divide by 0 errors.
+ String result = new RuleBasedNumberFormat(ruRules, new ULocale("ru")).format(21000);
+ if (!"двадцать один тысяча".equals(result)) {
+ errln("Got " + result + " for 21000");
+ }
+ }
public void TestFractionalRuleSet() {
RuleBasedNumberFormat formatter = new RuleBasedNumberFormat(fracRules,
logln("big dec: " + buf.toString());
}
- public void TestTrailingSemicolon() {
- String thaiRules =
- "%default:\n" +
- " -x: \u0e25\u0e1a>>;\n" +
- " x.x: <<\u0e08\u0e38\u0e14>>>;\n" +
- " \u0e28\u0e39\u0e19\u0e22\u0e4c; \u0e2b\u0e19\u0e36\u0e48\u0e07; \u0e2a\u0e2d\u0e07; \u0e2a\u0e32\u0e21;\n" +
- " \u0e2a\u0e35\u0e48; \u0e2b\u0e49\u0e32; \u0e2b\u0e01; \u0e40\u0e08\u0e47\u0e14; \u0e41\u0e1b\u0e14;\n" +
- " \u0e40\u0e01\u0e49\u0e32; \u0e2a\u0e34\u0e1a; \u0e2a\u0e34\u0e1a\u0e40\u0e2d\u0e47\u0e14;\n" +
- " \u0e2a\u0e34\u0e1a\u0e2a\u0e2d\u0e07; \u0e2a\u0e34\u0e1a\u0e2a\u0e32\u0e21;\n" +
- " \u0e2a\u0e34\u0e1a\u0e2a\u0e35\u0e48; \u0e2a\u0e34\u0e1a\u0e2b\u0e49\u0e32;\n" +
- " \u0e2a\u0e34\u0e1a\u0e2b\u0e01; \u0e2a\u0e34\u0e1a\u0e40\u0e08\u0e47\u0e14;\n" +
- " \u0e2a\u0e34\u0e1a\u0e41\u0e1b\u0e14; \u0e2a\u0e34\u0e1a\u0e40\u0e01\u0e49\u0e32;\n" +
- " 20: \u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 30: \u0e2a\u0e32\u0e21\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 40: \u0e2a\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 50: \u0e2b\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 60: \u0e2b\u0e01\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 70: \u0e40\u0e08\u0e47\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 80: \u0e41\u0e1b\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 90: \u0e40\u0e01\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
- " 100: <<\u0e23\u0e49\u0e2d\u0e22[>>];\n" +
- " 1000: <<\u0e1e\u0e31\u0e19[>>];\n" +
- " 10000: <<\u0e2b\u0e21\u0e37\u0e48\u0e19[>>];\n" +
- " 100000: <<\u0e41\u0e2a\u0e19[>>];\n" +
- " 1,000,000: <<\u0e25\u0e49\u0e32\u0e19[>>];\n" +
- " 1,000,000,000: <<\u0e1e\u0e31\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n" +
- " 1,000,000,000,000: <<\u0e25\u0e49\u0e32\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n" +
- " 1,000,000,000,000,000: =#,##0=;\n" +
- "%%alt-ones:\n" +
- " \u0e28\u0e39\u0e19\u0e22\u0e4c;\n" +
- " \u0e40\u0e2d\u0e47\u0e14;\n" +
- " =%default=;\n ; ;; ";
+ public void TestTrailingSemicolon() {
+ String thaiRules =
+ "%default:\n" +
+ " -x: \u0e25\u0e1a>>;\n" +
+ " x.x: <<\u0e08\u0e38\u0e14>>>;\n" +
+ " \u0e28\u0e39\u0e19\u0e22\u0e4c; \u0e2b\u0e19\u0e36\u0e48\u0e07; \u0e2a\u0e2d\u0e07; \u0e2a\u0e32\u0e21;\n" +
+ " \u0e2a\u0e35\u0e48; \u0e2b\u0e49\u0e32; \u0e2b\u0e01; \u0e40\u0e08\u0e47\u0e14; \u0e41\u0e1b\u0e14;\n" +
+ " \u0e40\u0e01\u0e49\u0e32; \u0e2a\u0e34\u0e1a; \u0e2a\u0e34\u0e1a\u0e40\u0e2d\u0e47\u0e14;\n" +
+ " \u0e2a\u0e34\u0e1a\u0e2a\u0e2d\u0e07; \u0e2a\u0e34\u0e1a\u0e2a\u0e32\u0e21;\n" +
+ " \u0e2a\u0e34\u0e1a\u0e2a\u0e35\u0e48; \u0e2a\u0e34\u0e1a\u0e2b\u0e49\u0e32;\n" +
+ " \u0e2a\u0e34\u0e1a\u0e2b\u0e01; \u0e2a\u0e34\u0e1a\u0e40\u0e08\u0e47\u0e14;\n" +
+ " \u0e2a\u0e34\u0e1a\u0e41\u0e1b\u0e14; \u0e2a\u0e34\u0e1a\u0e40\u0e01\u0e49\u0e32;\n" +
+ " 20: \u0e22\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 30: \u0e2a\u0e32\u0e21\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 40: \u0e2a\u0e35\u0e48\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 50: \u0e2b\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 60: \u0e2b\u0e01\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 70: \u0e40\u0e08\u0e47\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 80: \u0e41\u0e1b\u0e14\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 90: \u0e40\u0e01\u0e49\u0e32\u0e2a\u0e34\u0e1a[>%%alt-ones>];\n" +
+ " 100: <<\u0e23\u0e49\u0e2d\u0e22[>>];\n" +
+ " 1000: <<\u0e1e\u0e31\u0e19[>>];\n" +
+ " 10000: <<\u0e2b\u0e21\u0e37\u0e48\u0e19[>>];\n" +
+ " 100000: <<\u0e41\u0e2a\u0e19[>>];\n" +
+ " 1,000,000: <<\u0e25\u0e49\u0e32\u0e19[>>];\n" +
+ " 1,000,000,000: <<\u0e1e\u0e31\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n" +
+ " 1,000,000,000,000: <<\u0e25\u0e49\u0e32\u0e19\u0e25\u0e49\u0e32\u0e19[>>];\n" +
+ " 1,000,000,000,000,000: =#,##0=;\n" +
+ "%%alt-ones:\n" +
+ " \u0e28\u0e39\u0e19\u0e22\u0e4c;\n" +
+ " \u0e40\u0e2d\u0e47\u0e14;\n" +
+ " =%default=;\n ; ;; ";
RuleBasedNumberFormat formatter = new RuleBasedNumberFormat(thaiRules, new Locale("th", "TH", ""));
}
public void TestRuleSetDisplayName() {
+ /**
+ * Spellout rules for U.K. English.
+ * I borrow the rule sets for TestRuleSetDisplayName()
+ */
+ final String ukEnglish =
+ "%simplified:\n"
+ + " -x: minus >>;\n"
+ + " x.x: << point >>;\n"
+ + " zero; one; two; three; four; five; six; seven; eight; nine;\n"
+ + " ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n"
+ + " seventeen; eighteen; nineteen;\n"
+ + " 20: twenty[->>];\n"
+ + " 30: thirty[->>];\n"
+ + " 40: forty[->>];\n"
+ + " 50: fifty[->>];\n"
+ + " 60: sixty[->>];\n"
+ + " 70: seventy[->>];\n"
+ + " 80: eighty[->>];\n"
+ + " 90: ninety[->>];\n"
+ + " 100: << hundred[ >>];\n"
+ + " 1000: << thousand[ >>];\n"
+ + " 1,000,000: << million[ >>];\n"
+ + " 1,000,000,000,000: << billion[ >>];\n"
+ + " 1,000,000,000,000,000: =#,##0=;\n"
+ + "%alt-teens:\n"
+ + " =%simplified=;\n"
+ + " 1000>: <%%alt-hundreds<[ >>];\n"
+ + " 10,000: =%simplified=;\n"
+ + " 1,000,000: << million[ >%simplified>];\n"
+ + " 1,000,000,000,000: << billion[ >%simplified>];\n"
+ + " 1,000,000,000,000,000: =#,##0=;\n"
+ + "%%alt-hundreds:\n"
+ + " 0: SHOULD NEVER GET HERE!;\n"
+ + " 10: <%simplified< thousand;\n"
+ + " 11: =%simplified= hundred>%%empty>;\n"
+ + "%%empty:\n"
+ + " 0:;"
+ + "%ordinal:\n"
+ + " zeroth; first; second; third; fourth; fifth; sixth; seventh;\n"
+ + " eighth; ninth;\n"
+ + " tenth; eleventh; twelfth; thirteenth; fourteenth;\n"
+ + " fifteenth; sixteenth; seventeenth; eighteenth;\n"
+ + " nineteenth;\n"
+ + " twentieth; twenty->>;\n"
+ + " 30: thirtieth; thirty->>;\n"
+ + " 40: fortieth; forty->>;\n"
+ + " 50: fiftieth; fifty->>;\n"
+ + " 60: sixtieth; sixty->>;\n"
+ + " 70: seventieth; seventy->>;\n"
+ + " 80: eightieth; eighty->>;\n"
+ + " 90: ninetieth; ninety->>;\n"
+ + " 100: <%simplified< hundredth; <%simplified< hundred >>;\n"
+ + " 1000: <%simplified< thousandth; <%simplified< thousand >>;\n"
+ + " 1,000,000: <%simplified< millionth; <%simplified< million >>;\n"
+ + " 1,000,000,000,000: <%simplified< billionth;\n"
+ + " <%simplified< billion >>;\n"
+ + " 1,000,000,000,000,000: =#,##0=;"
+ + "%default:\n"
+ + " -x: minus >>;\n"
+ + " x.x: << point >>;\n"
+ + " =%simplified=;\n"
+ + " 100: << hundred[ >%%and>];\n"
+ + " 1000: << thousand[ >%%and>];\n"
+ + " 100,000>>: << thousand[>%%commas>];\n"
+ + " 1,000,000: << million[>%%commas>];\n"
+ + " 1,000,000,000,000: << billion[>%%commas>];\n"
+ + " 1,000,000,000,000,000: =#,##0=;\n"
+ + "%%and:\n"
+ + " and =%default=;\n"
+ + " 100: =%default=;\n"
+ + "%%commas:\n"
+ + " ' and =%default=;\n"
+ + " 100: , =%default=;\n"
+ + " 1000: , <%default< thousand, >%default>;\n"
+ + " 1,000,000: , =%default=;"
+ + "%%lenient-parse:\n"
+ + " & ' ' , ',' ;\n";
ULocale.setDefault(ULocale.US);
String[][] localizations = new String[][] {
/* public rule sets*/
}
public void TestAllLocales() {
- StringBuffer errors = new StringBuffer();
+ StringBuilder errors = new StringBuilder();
String[] names = {
" (spellout) ",
" (ordinal) "
if (c < numbers.length) {
n = numbers[c];
} else {
- n = ((int)(r.nextInt(10000) - 3000)) / 16d;
+ n = (r.nextInt(10000) - 3000) / 16d;
}
String s = fmt.format(n);
- logln(loc.getName() + names[j] + "success format: " + n + " -> " + s);
+ if (isVerbose()) {
+ logln(loc.getName() + names[j] + "success format: " + n + " -> " + s);
+ }
try {
// RBNF parse is extremely slow when lenient option is enabled.
// non-lenient parse
fmt.setLenientParseMode(false);
Number num = fmt.parse(s);
- logln(loc.getName() + names[j] + "success parse: " + s + " -> " + num);
+ if (isVerbose()) {
+ logln(loc.getName() + names[j] + "success parse: " + s + " -> " + num);
+ }
if (j != 0) {
// TODO: Fix the ordinal rules.
continue;
for (int i = 0; i < testData.length; i++) {
String number = testData[i][0];
String expectedWords = testData[i][1];
- logln("test[" + i + "] number: " + number + " target: " + expectedWords);
+ if (isVerbose()) {
+ logln("test[" + i + "] number: " + number + " target: " + expectedWords);
+ }
Number num = decFmt.parse(number);
String actualWords = formatter.format(num);
}
}
- /**
- * Spellout rules for U.K. English.
- * I borrow the rule sets for TestRuleSetDisplayName()
- */
- public static final String ukEnglish =
- "%simplified:\n"
- + " -x: minus >>;\n"
- + " x.x: << point >>;\n"
- + " zero; one; two; three; four; five; six; seven; eight; nine;\n"
- + " ten; eleven; twelve; thirteen; fourteen; fifteen; sixteen;\n"
- + " seventeen; eighteen; nineteen;\n"
- + " 20: twenty[->>];\n"
- + " 30: thirty[->>];\n"
- + " 40: forty[->>];\n"
- + " 50: fifty[->>];\n"
- + " 60: sixty[->>];\n"
- + " 70: seventy[->>];\n"
- + " 80: eighty[->>];\n"
- + " 90: ninety[->>];\n"
- + " 100: << hundred[ >>];\n"
- + " 1000: << thousand[ >>];\n"
- + " 1,000,000: << million[ >>];\n"
- + " 1,000,000,000,000: << billion[ >>];\n"
- + " 1,000,000,000,000,000: =#,##0=;\n"
- + "%alt-teens:\n"
- + " =%simplified=;\n"
- + " 1000>: <%%alt-hundreds<[ >>];\n"
- + " 10,000: =%simplified=;\n"
- + " 1,000,000: << million[ >%simplified>];\n"
- + " 1,000,000,000,000: << billion[ >%simplified>];\n"
- + " 1,000,000,000,000,000: =#,##0=;\n"
- + "%%alt-hundreds:\n"
- + " 0: SHOULD NEVER GET HERE!;\n"
- + " 10: <%simplified< thousand;\n"
- + " 11: =%simplified= hundred>%%empty>;\n"
- + "%%empty:\n"
- + " 0:;"
- + "%ordinal:\n"
- + " zeroth; first; second; third; fourth; fifth; sixth; seventh;\n"
- + " eighth; ninth;\n"
- + " tenth; eleventh; twelfth; thirteenth; fourteenth;\n"
- + " fifteenth; sixteenth; seventeenth; eighteenth;\n"
- + " nineteenth;\n"
- + " twentieth; twenty->>;\n"
- + " 30: thirtieth; thirty->>;\n"
- + " 40: fortieth; forty->>;\n"
- + " 50: fiftieth; fifty->>;\n"
- + " 60: sixtieth; sixty->>;\n"
- + " 70: seventieth; seventy->>;\n"
- + " 80: eightieth; eighty->>;\n"
- + " 90: ninetieth; ninety->>;\n"
- + " 100: <%simplified< hundredth; <%simplified< hundred >>;\n"
- + " 1000: <%simplified< thousandth; <%simplified< thousand >>;\n"
- + " 1,000,000: <%simplified< millionth; <%simplified< million >>;\n"
- + " 1,000,000,000,000: <%simplified< billionth;\n"
- + " <%simplified< billion >>;\n"
- + " 1,000,000,000,000,000: =#,##0=;"
- + "%default:\n"
- + " -x: minus >>;\n"
- + " x.x: << point >>;\n"
- + " =%simplified=;\n"
- + " 100: << hundred[ >%%and>];\n"
- + " 1000: << thousand[ >%%and>];\n"
- + " 100,000>>: << thousand[>%%commas>];\n"
- + " 1,000,000: << million[>%%commas>];\n"
- + " 1,000,000,000,000: << billion[>%%commas>];\n"
- + " 1,000,000,000,000,000: =#,##0=;\n"
- + "%%and:\n"
- + " and =%default=;\n"
- + " 100: =%default=;\n"
- + "%%commas:\n"
- + " ' and =%default=;\n"
- + " 100: , =%default=;\n"
- + " 1000: , <%default< thousand, >%default>;\n"
- + " 1,000,000: , =%default=;"
- + "%%lenient-parse:\n"
- + " & ' ' , ',' ;\n";
-
/* Tests the method
* public boolean equals(Object that)
*/
public void TestEquals(){
// Tests when "if (!(that instanceof RuleBasedNumberFormat))" is true
RuleBasedNumberFormat rbnf = new RuleBasedNumberFormat("dummy");
- if (rbnf.equals(new String("dummy")) != false ||
- rbnf.equals(new Character('a')) != false ||
- rbnf.equals(new Object()) != false ||
- rbnf.equals(-1) != false ||
- rbnf.equals(0) != false ||
- rbnf.equals(1) != false ||
- rbnf.equals(-1.0) != false ||
- rbnf.equals(0.0) != false ||
- rbnf.equals(1.0) != false)
+ if (rbnf.equals("dummy") ||
+ rbnf.equals(new Character('a')) ||
+ rbnf.equals(new Object()) ||
+ rbnf.equals(-1) ||
+ rbnf.equals(0) ||
+ rbnf.equals(1) ||
+ rbnf.equals(-1.0) ||
+ rbnf.equals(0.0) ||
+ rbnf.equals(1.0))
{
errln("RuleBasedNumberFormat.equals(Object that) was suppose to " +
"be false for an invalid object.");
RuleBasedNumberFormat rbnf3 = new RuleBasedNumberFormat("dummy", new Locale("sp"));
RuleBasedNumberFormat rbnf4 = new RuleBasedNumberFormat("dummy", new Locale("fr"));
- if(rbnf1.equals(rbnf2) != false || rbnf1.equals(rbnf3) != false ||
- rbnf1.equals(rbnf4) != false || rbnf2.equals(rbnf3) != false ||
- rbnf2.equals(rbnf4) != false || rbnf3.equals(rbnf4) != false){
+ if(rbnf1.equals(rbnf2) || rbnf1.equals(rbnf3) ||
+ rbnf1.equals(rbnf4) || rbnf2.equals(rbnf3) ||
+ rbnf2.equals(rbnf4) || rbnf3.equals(rbnf4)){
errln("RuleBasedNumberFormat.equals(Object that) was suppose to " +
"be false for an invalid object.");
}
- if(rbnf1.equals(rbnf1) == false){
+ if(!rbnf1.equals(rbnf1)){
errln("RuleBasedNumberFormat.equals(Object that) was not suppose to " +
"be false for an invalid object.");
}
- if(rbnf2.equals(rbnf2) == false){
+ if(!rbnf2.equals(rbnf2)){
errln("RuleBasedNumberFormat.equals(Object that) was not suppose to " +
"be false for an invalid object.");
}
- if(rbnf3.equals(rbnf3) == false){
+ if(!rbnf3.equals(rbnf3)){
errln("RuleBasedNumberFormat.equals(Object that) was not suppose to " +
"be false for an invalid object.");
}
- if(rbnf4.equals(rbnf4) == false){
+ if(!rbnf4.equals(rbnf4)){
errln("RuleBasedNumberFormat.equals(Object that) was not suppose to " +
"be false for an invalid object.");
}
RuleBasedNumberFormat rbnf5 = new RuleBasedNumberFormat("dummy", new Locale("en"));
RuleBasedNumberFormat rbnf6 = new RuleBasedNumberFormat("dummy", new Locale("en"));
- if(rbnf5.equals(rbnf6) == false){
+ if(!rbnf5.equals(rbnf6)){
errln("RuleBasedNumberFormat.equals(Object that) was not suppose to " +
"be false for an invalid object.");
}
rbnf6.setLenientParseMode(true);
- if(rbnf5.equals(rbnf6) != false){
+ if(rbnf5.equals(rbnf6)){
errln("RuleBasedNumberFormat.equals(Object that) was suppose to " +
"be false for an invalid object.");
}
// Tests when "if (!ruleSets[i].equals(that2.ruleSets[i]))" is true
RuleBasedNumberFormat rbnf7 = new RuleBasedNumberFormat("not_dummy", new Locale("en"));
- if(rbnf5.equals(rbnf7) != false){
+ if(rbnf5.equals(rbnf7)){
errln("RuleBasedNumberFormat.equals(Object that) was suppose to " +
"be false for an invalid object.");
}
value = val;
expectedResult = expRes;
}
- };
+ }
final TextContextItem[] items = {
new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE, 123.45, "ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),
new TextContextItem( "sv", RuleBasedNumberFormat.SPELLOUT, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE, 123.45, "Ett\u00ADhundra\u00ADtjugo\u00ADtre komma fyra fem" ),