From 29bfb5542a355a3f6068ed3d01ef5ba9ebb3dab3 Mon Sep 17 00:00:00 2001 From: Alan Liu Date: Thu, 15 Apr 2004 06:03:00 +0000 Subject: [PATCH] ICU-2825 add support for formatting and parsing CurrencyAmount objects X-SVN-Rev: 14973 --- .../icu/dev/test/format/NumberFormatTest.java | 29 ++- .../dev/test/format/NumberFormatTestCases.txt | 15 ++ icu4j/src/com/ibm/icu/text/DecimalFormat.java | 200 +++++++++++++----- icu4j/src/com/ibm/icu/text/NumberFormat.java | 122 ++++++++--- icu4j/src/com/ibm/icu/util/Currency.java | 121 ++++++++++- 5 files changed, 410 insertions(+), 77 deletions(-) diff --git a/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 1098dfd3a2c..74df594021b 100755 --- a/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -970,6 +970,18 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { } } + /** + * Parse a CurrencyAmount using the given NumberFormat, with + * the 'delim' character separating the number and the currency. + */ + private static CurrencyAmount parseCurrencyAmount(String str, NumberFormat fmt, + char delim) + throws ParseException { + int i = str.indexOf(delim); + return new CurrencyAmount(fmt.parse(str.substring(0,i)), + Currency.getInstance(str.substring(i+1))); + } + /** * Return an integer representing the next token from this * iterator. The integer will be an index into the given list, or @@ -992,8 +1004,9 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { /*3*/ "fp:", // /*4*/ "rt:", // <(exp.) number> <(exp.) string> /*5*/ "p:", // - /*6*/ "perr:", // + /*6*/ "perr:", // /*7*/ "pat:", // + /*8*/ "fpc:", // }; public void TestCases() { @@ -1027,6 +1040,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { case 3: // fp: case 4: // rt: case 5: // p: + case 8: // fpc: tok = tokens.next(); if (!tok.equals("-")) { pat = tok; @@ -1037,7 +1051,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { iae.printStackTrace(); tokens.next(); // consume remaining tokens tokens.next(); - if (cmd == 3) tokens.next(); + if (cmd == 3 || cmd == 8) tokens.next(); continue; } } @@ -1060,6 +1074,17 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { n, fmt.parse(str)); } } + // fpc: + else if (cmd == 8) { + String currAmt = tokens.next(); + str = tokens.next(); + CurrencyAmount n = parseCurrencyAmount(currAmt, ref, '/'); + assertEquals(where + '"' + pat + "\".format(" + currAmt + ")", + str, fmt.format(n)); + n = parseCurrencyAmount(tokens.next(), ref, '/'); + assertEquals(where + '"' + pat + "\".parse(\"" + str + "\")", + n, fmt.parseCurrency(str)); + } // p: else { str = tokens.next(); diff --git a/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTestCases.txt b/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTestCases.txt index 8398a54d260..b2d940ad789 100644 --- a/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTestCases.txt +++ b/icu4j/src/com/ibm/icu/dev/test/format/NumberFormatTestCases.txt @@ -57,6 +57,8 @@ rt: - 3 "3.0" rt: - 5000 "5000" rt: - 0.005 "0.0050" pat: - - +fp: "@@@@@@" 123456.7 "123457" 123457 +pat: - - pat: "##,@@##" "#,@@##" pat: "##@@##" "@@##" @@ -68,3 +70,16 @@ pat: "@@0" err # either @ or 0, not both # NumberRegression/Test4140009 rt: "" 123.456 "123.456" rt: "" -123.456 "-123.456" + +# Currency +fpc: "\u00A4 0.00" 1234.56/USD "$ 1234.56" 1234.56/USD +fpc: - 1234.56/JPY "\u00A5 1235" 1235/JPY +# ISO codes that overlap display names (QQQ vs. Q) +fpc: - 123/QQQ "QQQ 123.00" 123/QQQ # QQQ is fake +fpc: - 123/GTQ "Q 123.00" 123/GTQ +# ChoiceFormat-based display names +fpc: - 1/INR "Re. 1.00" 1/INR +fpc: - 2/INR "Rs. 2.00" 2/INR +# Display names with shared prefix (YDD vs. Y) +fpc: - 100/YDD "YDD 100.00" 100/YDD +fpc: - 100/CNY "Y 100.00" 100/CNY diff --git a/icu4j/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/src/com/ibm/icu/text/DecimalFormat.java index cac1fe72ea5..9709a923187 100755 --- a/icu4j/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/src/com/ibm/icu/text/DecimalFormat.java @@ -7,6 +7,8 @@ package com.ibm.icu.text; import com.ibm.icu.util.Currency; +import com.ibm.icu.util.CurrencyAmount; +import com.ibm.icu.util.ULocale; import com.ibm.icu.lang.UCharacter; import com.ibm.icu.impl.UCharacterProperty; import java.text.ParsePosition; @@ -1309,8 +1311,42 @@ public class DecimalFormat extends NumberFormat { * null if the parse failed * @stable ICU 2.0 */ - public Number parse(String text, ParsePosition parsePosition) - { + public Number parse(String text, ParsePosition parsePosition) { + return (Number) parse(text, parsePosition, false); + } + + /** + * NEW + * Parses text from the given string as a CurrencyAmount. This + * method will fail if this format is not a currency format, that + * is, if it does not contain the currency pattern symbol (U+00A4) + * in its prefix or suffix. + * + * @param text the string to parse + * @param pos input-output position; on input, the position within + * text to match; must have 0 <= pos.getIndex() < text.length(); + * on output, the position after the last matched character. If + * the parse fails, the position in unchanged upon output. + * @return a CurrencyAmount, or null upon failure + * @draft ICU 3.0 + */ + public CurrencyAmount parseCurrency(String text, ParsePosition pos) { + return (CurrencyAmount) parse(text, pos, true); + } + + /** + * Parses the given text as either a Number or a CurrencyAmount. + * @param text the string to parse + * @param parsePosition input-output position; on input, the + * position within text to match; must have 0 <= pos.getIndex() < + * text.length(); on output, the position after the last matched + * character. If the parse fails, the position in unchanged upon + * output. + * @param parseCurrency if true, a CurrencyAmount is parsed and + * returned; otherwise a Number is parsed and returned + * @return a Number or CurrencyAmount or null + */ + private Object parse(String text, ParsePosition parsePosition, boolean parseCurrency) { int backup; int i = backup = parsePosition.getIndex(); @@ -1337,46 +1373,55 @@ public class DecimalFormat extends NumberFormat { i = backup; boolean[] status = new boolean[STATUS_LENGTH]; - if (!subparse(text, parsePosition, digitList, false, status)) { + Currency[] currency = parseCurrency ? new Currency[1] : null; + if (!subparse(text, parsePosition, digitList, false, status, currency)) { parsePosition.setIndex(backup); return null; } + Number n = null; + // Handle infinity if (status[STATUS_INFINITE]) { - return new Double(status[STATUS_POSITIVE] - ? Double.POSITIVE_INFINITY - : Double.NEGATIVE_INFINITY); + n = new Double(status[STATUS_POSITIVE] + ? Double.POSITIVE_INFINITY + : Double.NEGATIVE_INFINITY); } // Handle -0.0 - if (!status[STATUS_POSITIVE] && digitList.isZero()) { - return new Double(-0.0); + else if (!status[STATUS_POSITIVE] && digitList.isZero()) { + n = new Double(-0.0); } - // Do as much of the multiplier conversion as possible without - // losing accuracy. - int mult = multiplier; // Don't modify this.multiplier - while (mult % 10 == 0) { - --digitList.decimalAt; - mult /= 10; + else { + // Do as much of the multiplier conversion as possible without + // losing accuracy. + int mult = multiplier; // Don't modify this.multiplier + while (mult % 10 == 0) { + --digitList.decimalAt; + mult /= 10; + } + + // Handle integral values + if (mult == 1 && digitList.isIntegral()) { + BigInteger big = digitList.getBigInteger(status[STATUS_POSITIVE]); + n = (big.bitLength() < 64) ? + (Number) new Long(big.longValue()) : (Number) big; + } + + // Handle non-integral values + else { + java.math.BigDecimal big = digitList.getBigDecimal(status[STATUS_POSITIVE]); + n = big; + if (mult != 1) { + n = big.divide(java.math.BigDecimal.valueOf(mult), + java.math.BigDecimal.ROUND_HALF_EVEN); + } + } } - // Handle integral values - if (mult == 1 && digitList.isIntegral()) { - BigInteger n = digitList.getBigInteger(status[STATUS_POSITIVE]); - return (n.bitLength() < 64) - ? (Number) new Long(n.longValue()) - : (Number) n; - } - - // Handle non-integral values - java.math.BigDecimal n = digitList.getBigDecimal(status[STATUS_POSITIVE]); - if (mult != 1) { - n = n.divide(java.math.BigDecimal.valueOf(mult), - java.math.BigDecimal.ROUND_HALF_EVEN); - } - return n; + // Assemble into CurrencyAmount if necessary + return parseCurrency ? new CurrencyAmount(n, currency[0]) : n; } private static final int STATUS_INFINITE = 0; @@ -1395,10 +1440,14 @@ public class DecimalFormat extends NumberFormat { * infinite values and integer only. * @param status Upon return contains boolean status flags indicating * whether the value was infinite and whether it was positive. + * @param currency return value for parsed currency, for generic + * currency parsing mode, or null for normal parsing. In generic + * currency parsing mode, any currency is parsed, not just the + * currency that this formatter is set to. */ private final boolean subparse(String text, ParsePosition parsePosition, DigitList digits, boolean isExponent, - boolean status[]) + boolean status[], Currency currency[]) { int position = parsePosition.getIndex(); int oldStart = parsePosition.getIndex(); @@ -1409,8 +1458,8 @@ public class DecimalFormat extends NumberFormat { } // Match positive and negative prefixes; prefer longest match. - int posMatch = compareAffix(text, position, false, true); - int negMatch = compareAffix(text, position, true, true); + int posMatch = compareAffix(text, position, false, true, currency); + int negMatch = compareAffix(text, position, true, true, currency); if (posMatch >= 0 && negMatch >= 0) { if (posMatch > negMatch) { negMatch = -1; @@ -1611,10 +1660,10 @@ public class DecimalFormat extends NumberFormat { // Match positive and negative suffixes; prefer longest match. if (posMatch >= 0) { - posMatch = compareAffix(text, position, false, false); + posMatch = compareAffix(text, position, false, false, currency); } if (negMatch >= 0) { - negMatch = compareAffix(text, position, true, false); + negMatch = compareAffix(text, position, true, false, currency); } if (posMatch >= 0 && negMatch >= 0) { if (posMatch > negMatch) { @@ -1669,17 +1718,21 @@ public class DecimalFormat extends NumberFormat { * @param pos offset into input at which to begin matching * @param isNegative * @param isPrefix + * @param currency return value for parsed currency, for generic + * currency parsing mode, or null for normal parsing. In generic + * currency parsing mode, any currency is parsed, not just the + * currency that this formatter is set to. * @return length of input that matches, or -1 if match failure */ - private int compareAffix(String text, int pos, - boolean isNegative, boolean isPrefix) { - if (currencyChoice != null) { + private int compareAffix(String text, int pos, boolean isNegative, + boolean isPrefix, Currency[] currency) { + if (currency != null || currencyChoice != null) { if (isPrefix) { return compareComplexAffix(isNegative ? negPrefixPattern : posPrefixPattern, - text, pos); + text, pos, currency); } else { return compareComplexAffix(isNegative ? negSuffixPattern : posSuffixPattern, - text, pos); + text, pos, currency); } } @@ -1789,9 +1842,14 @@ public class DecimalFormat extends NumberFormat { * @param affixPat pattern string * @param text input text * @param pos offset into input at which to begin matching + * @param currency return value for parsed currency, for generic + * currency parsing mode, or null for normal parsing. In generic + * currency parsing mode, any currency is parsed, not just the + * currency that this formatter is set to. * @return length of input that matches, or -1 if match failure */ - private int compareComplexAffix(String affixPat, String text, int pos) { + private int compareComplexAffix(String affixPat, String text, int pos, + Currency[] currency) { for (int i=0; i= 0; ) { char c = affixPat.charAt(i++); @@ -1824,17 +1882,43 @@ public class DecimalFormat extends NumberFormat { switch (c) { case CURRENCY_SIGN: - // assert(currency != null); - // assert(currencyChoice != null); + // If currency != null, then perform generic currency matching. + // Otherwise, do currency choice parsing. + //assert(currency != null || + // (getCurrency() != null && currencyChoice != null)); boolean intl = i ISO code to Currency ParsePosition ppos = new ParsePosition(pos); - /* Number n = */currencyChoice.parse(text, ppos); - pos = (ppos.getIndex() == pos) ? -1 : ppos.getIndex(); + String iso = Currency.parse(uloc.toLocale(), text, ppos); + + // If parse succeeds, populate currency[0] + if (iso != null) { + currency[0] = Currency.getInstance(iso); + pos = ppos.getIndex(); + } else { + pos = -1; + } + } else { + if (intl) { + ++i; + pos = match(text, pos, getCurrency().getCurrencyCode()); + } else { + ParsePosition ppos = new ParsePosition(pos); + /* Number n = */currencyChoice.parse(text, ppos); + pos = (ppos.getIndex() == pos) ? -1 : ppos.getIndex(); + } } continue; case PATTERN_PERCENT: @@ -2526,6 +2610,10 @@ public class DecimalFormat extends NumberFormat { && useExponentialNotation == other.useExponentialNotation && (!useExponentialNotation || minExponentDigits == other.minExponentDigits) + && useSignificantDigits == other.useSignificantDigits + && (!useSignificantDigits || + minSignificantDigits == other.minSignificantDigits && + maxSignificantDigits == other.maxSignificantDigits) && symbols.equals(other.symbols)); } @@ -3683,12 +3771,24 @@ public class DecimalFormat extends NumberFormat { setMinimumFractionDigits(d); setMaximumFractionDigits(d); } - - expandAffixes(); } } + /** + * Returns the currency in effect for this formatter. Subclasses + * should override this method as needed. Unlike getCurrency(), + * this method should never return null. + * @internal + */ + protected Currency getEffectiveCurrency() { + Currency c = getCurrency(); + if (c == null) { + c = Currency.getInstance(symbols.getInternationalCurrencySymbol()); + } + return c; + } + /** * Sets the maximum number of digits allowed in the fraction portion of a * number. This override limits the fraction digit count to 340. diff --git a/icu4j/src/com/ibm/icu/text/NumberFormat.java b/icu4j/src/com/ibm/icu/text/NumberFormat.java index d5c006ef42a..62a19011c1a 100755 --- a/icu4j/src/com/ibm/icu/text/NumberFormat.java +++ b/icu4j/src/com/ibm/icu/text/NumberFormat.java @@ -22,6 +22,7 @@ import java.util.Set; import com.ibm.icu.impl.ICULocaleData; import com.ibm.icu.impl.LocaleUtility; import com.ibm.icu.util.Currency; +import com.ibm.icu.util.CurrencyAmount; import com.ibm.icu.util.ULocale; import com.ibm.icu.text.UFormat; @@ -193,6 +194,8 @@ public abstract class NumberFormat extends UFormat { return format((java.math.BigDecimal) number, toAppendTo, pos); } else if (number instanceof com.ibm.icu.math.BigDecimal) { return format((com.ibm.icu.math.BigDecimal) number, toAppendTo, pos); + } else if (number instanceof CurrencyAmount) { + return format((CurrencyAmount)number, toAppendTo, pos); } else if (number instanceof Number) { return format(((Number)number).doubleValue(), toAppendTo, pos); } else { @@ -209,22 +212,22 @@ public abstract class NumberFormat extends UFormat { return parse(source, parsePosition); } - /** + /** * Specialization of format. * @see java.text.Format#format(Object) * @stable ICU 2.0 */ - public final String format (double number) { + public final String format(double number) { return format(number,new StringBuffer(), new FieldPosition(0)).toString(); } - /** + /** * Specialization of format. * @see java.text.Format#format(Object) * @stable ICU 2.0 */ - public final String format (long number) { + public final String format(long number) { return format(number,new StringBuffer(), new FieldPosition(0)).toString(); } @@ -259,7 +262,17 @@ public abstract class NumberFormat extends UFormat { new FieldPosition(0)).toString(); } - /** + /** + * NEW + * Convenience method to format a CurrencyAmount. + * @draft ICU 3.0 + */ + public final String format(CurrencyAmount currAmt) { + return format(currAmt, new StringBuffer(), + new FieldPosition(0)).toString(); + } + + /** * Specialization of format. * @see java.text.Format#format(Object, StringBuffer, FieldPosition) * @stable ICU 2.0 @@ -268,7 +281,7 @@ public abstract class NumberFormat extends UFormat { StringBuffer toAppendTo, FieldPosition pos); - /** + /** * Specialization of format. * @see java.text.Format#format(Object, StringBuffer, FieldPosition) * @stable ICU 2.0 @@ -306,8 +319,26 @@ public abstract class NumberFormat extends UFormat { public abstract StringBuffer format(com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos); - - /** + + /** + * NEW + * Format a CurrencyAmount. + * @see java.text.Format#format(Object, StringBuffer, FieldPosition) + * @draft ICU 3.0 + */ + public StringBuffer format(CurrencyAmount currAmt, + StringBuffer toAppendTo, + FieldPosition pos) { + // Default implementation -- subclasses may override + Currency save = getCurrency(), curr = currAmt.getCurrency(); + boolean same = curr.equals(save); + if (!same) setCurrency(curr); + format(currAmt.getNumber(), toAppendTo, pos); + if (!same) setCurrency(save); + return toAppendTo; + } + + /** * Returns a Long if possible (e.g., within the range [Long.MIN_VALUE, * Long.MAX_VALUE] and with no decimals), otherwise a Double. * If IntegerOnly is set, will stop at a decimal @@ -337,9 +368,53 @@ public abstract class NumberFormat extends UFormat { ParsePosition parsePosition = new ParsePosition(0); Number result = parse(text, parsePosition); if (parsePosition.getIndex() == 0) { - throw new ParseException("Unparseable number: \"" + text + "\"", - //PP:parsePosition.errorIndex); - 0); + throw new ParseException("Unparseable number: \"" + text + '"', + parsePosition.getErrorIndex()); + } + return result; + } + + /** + * NEW + * Parses text from the given string as a CurrencyAmount. This + * method will fail if this format is not a currency format, that + * is, if it does not contain the currency pattern symbol (U+00A4) + * in its prefix or suffix. + * + * @param text the string to parse + * @param pos input-output position; on input, the position within + * text to match; must have 0 <= pos.getIndex() < text.length(); + * on output, the position after the last matched character. If + * the parse fails, the position in unchanged upon output. + * @return a CurrencyAmount, or null upon failure + * @draft ICU 3.0 + */ + public CurrencyAmount parseCurrency(String text, ParsePosition pos) { + // Default implementation only -- subclasses should override + Number n = parse(text, pos); + return n == null ? null : new CurrencyAmount(n, getEffectiveCurrency()); + } + + /** + * NEW + * Parses text from the beginning of the given string as a + * CurrencyAmount. The method might not use the entire text of + * the given string. This method will fail if this format is not + * a currency format, that is, if it does not contain the currency + * pattern symbol (U+00A4) in its prefix or suffix. + * + * @param text the string to parse + * @return a non-null CurrencyAmount + * @exception ParseException if the beginning of the specified string + * cannot be parsed. + * @draft ICU 3.0 + */ + public CurrencyAmount parseCurrency(String text) throws ParseException { + ParsePosition pos = new ParsePosition(0); + CurrencyAmount result = parseCurrency(text, pos); + if (pos.getIndex() == 0) { + throw new ParseException("Unparseable currency: \"" + text + '"', + pos.getErrorIndex()); } return result; } @@ -912,21 +987,20 @@ public abstract class NumberFormat extends UFormat { } /** - * Sets the minimum integer digits count directly, with no range - * pinning. For use by subclasses. + * Returns the currency in effect for this formatter. Subclasses + * should override this method as needed. Unlike getCurrency(), + * this method should never return null. + * @return a non-null Currency * @internal */ - protected void internalSetMinimumIntegerDigits(int n) { - minimumIntegerDigits = n; - } - - /** - * Sets the maximum integer digits count directly, with no range - * pinning. For use by subclasses. - * @internal - */ - protected void internalSetMaximumIntegerDigits(int n) { - maximumIntegerDigits = n; + protected Currency getEffectiveCurrency() { + Currency c = getCurrency(); + if (c == null) { + ULocale uloc = getLocale(ULocale.VALID_LOCALE); + Locale loc = uloc != null ? uloc.toLocale() : Locale.getDefault(); + c = Currency.getInstance(loc); + } + return c; } // =======================privates=============================== diff --git a/icu4j/src/com/ibm/icu/util/Currency.java b/icu4j/src/com/ibm/icu/util/Currency.java index 4085361c2f2..9afea16b8fb 100644 --- a/icu4j/src/com/ibm/icu/util/Currency.java +++ b/icu4j/src/com/ibm/icu/util/Currency.java @@ -1,12 +1,14 @@ /** ******************************************************************************* - * Copyright (C) 2001-2003, International Business Machines Corporation and * + * Copyright (C) 2001-2004, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.util; import java.io.Serializable; +import java.text.ChoiceFormat; +import java.text.ParsePosition; import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -308,6 +310,123 @@ public class Currency implements Serializable { return isoCode; } + /** + * Attempt to parse the given string as a currency, either as a + * display name in the given locale, or as a 3-letter ISO 4217 + * code. If multiple display names match, then the longest one is + * selected. If both a display name and a 3-letter ISO code + * match, then the display name is preferred, unless it's length + * is less than 3. + * + * @param locale the locale of the display names to match + * @param text the text to parse + * @param pos input-output position; on input, the position within + * text to match; must have 0 <= pos.getIndex() < text.length(); + * on output, the position after the last matched character. If + * the parse fails, the position in unchanged upon output. + * @return the ISO 4217 code, as a string, of the best match, or + * null if there is no match + * + * @internal + */ + public static String parse(Locale locale, String text, ParsePosition pos) { + + // TODO: There is a slight problem with the pseudo-multi-level + // fallback implemented here. More-specific locales don't + // properly shield duplicate entries in less-specific locales. + // This problem will go away when real multi-level fallback is + // implemented. We could also fix this by recording (in a + // hash) which codes are used at each level of fallback, but + // this doesn't seem warranted. + + int start = pos.getIndex(); + String fragment = text.substring(start); + + String iso = null; + int max = 0; + + // Look up the Currencies resource for the given locale. The + // Currencies locale data looks like this: + //|en { + //| Currencies { + //| USD { "US$", "US Dollar" } + //| CHF { "Sw F", "Swiss Franc" } + //| INR { "=0#Rs|1#Re|1 max) { + iso = (String) currencies[i][0]; + max = len; + } + pos.setIndex(start); + continue; + } + } + if (name.length() > max && fragment.startsWith(name)) { + iso = (String) currencies[i][0]; + max = name.length(); + } + } + } + catch (MissingResourceException e) {} + + locale = LocaleUtility.fallback(locale); + } + + // If display name parse fails or if it matches fewer than 3 + // characters, try to parse 3-letter ISO. Do this after the + // display name processing so 3-letter display names are + // preferred. Consider /[A-Z]{3}/ to be valid ISO, and parse + // it manually--UnicodeSet/regex are too slow and heavy. + if (max < 3 && (text.length() - start) >= 3) { + boolean valid = true; + for (int k=0; k<3; ++k) { + char ch = text.charAt(start + k); // 16-bit ok + if (ch < 'A' || ch > 'Z') { + valid = false; + break; + } + } + if (valid) { + iso = text.substring(start, start+3); + max = 3; + } + } + + pos.setIndex(start + max); + return iso; + } + /** * Returns the number of the number of fraction digits that should * be displayed for this currency.