X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/729e4ab9bc6618bc3d8a898e575df7f4019e29ca..249c4c5ea9376c24572daf9c2effa7484a282f14:/icuSources/i18n/rbnf.cpp diff --git a/icuSources/i18n/rbnf.cpp b/icuSources/i18n/rbnf.cpp index 3922d312..f7402b59 100644 --- a/icuSources/i18n/rbnf.cpp +++ b/icuSources/i18n/rbnf.cpp @@ -1,17 +1,21 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* -* Copyright (C) 1997-2010, International Business Machines Corporation +* Copyright (C) 1997-2015, International Business Machines Corporation * and others. All Rights Reserved. ******************************************************************************* */ -#include // for 'typeid' to work +#include "unicode/utypes.h" +#include "utypeinfo.h" // for 'typeid' to work #include "unicode/rbnf.h" #if U_HAVE_RBNF #include "unicode/normlzr.h" +#include "unicode/plurfmt.h" #include "unicode/tblcoll.h" #include "unicode/uchar.h" #include "unicode/ucol.h" @@ -21,18 +25,22 @@ #include "unicode/ustring.h" #include "unicode/utf16.h" #include "unicode/udata.h" -#include "nfrs.h" +#include "unicode/udisplaycontext.h" +#include "unicode/brkiter.h" +#include "unicode/ucasemap.h" #include "cmemory.h" #include "cstring.h" -#include "util.h" +#include "patternprops.h" #include "uresimp.h" +#include "nfrs.h" +#include "digitlst.h" // debugging -// #define DEBUG +// #define RBNF_DEBUG -#ifdef DEBUG -#include "stdio.h" +#ifdef RBNF_DEBUG +#include #endif #define U_ICUDATA_RBNF U_ICUDATA_NAME U_TREE_SEPARATOR_STRING "rbnf" @@ -58,10 +66,6 @@ static const UChar gSemiPercent[] = #define kHalfMaxDouble (double)(1 << kSomeNumberOfBitsDiv2) #define kMaxDouble (kHalfMaxDouble * kHalfMaxDouble) -// Temporary workaround - when noParse is true, do noting in parse. -// TODO: We need a real fix - see #6895/#6896 -static const char *NO_SPELLOUT_PARSE_LANGUAGES[] = { "ga", NULL }; - U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedNumberFormat) @@ -75,7 +79,7 @@ The RTTI code was also removed due to lack of code coverage. */ class LocalizationInfo : public UMemory { protected: - virtual ~LocalizationInfo() {}; + virtual ~LocalizationInfo(); uint32_t refcount; public: @@ -109,6 +113,8 @@ public: // static UClassID getStaticClassID(void); }; +LocalizationInfo::~LocalizationInfo() {} + //UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(LocalizationInfo) // if both strings are NULL, this returns TRUE @@ -310,13 +316,33 @@ public: private: - void inc(void) { ++p; ch = 0xffff; } - UBool checkInc(UChar c) { if (p < e && (ch == c || *p == c)) { inc(); return TRUE; } return FALSE; } - UBool check(UChar c) { return p < e && (ch == c || *p == c); } - void skipWhitespace(void) { while (p < e && uprv_isRuleWhiteSpace(ch != 0xffff ? ch : *p)) inc();} - UBool inList(UChar c, const UChar* list) const { - if (*list == SPACE && uprv_isRuleWhiteSpace(c)) return TRUE; - while (*list && *list != c) ++list; return *list == c; + inline void inc(void) { + ++p; + ch = 0xffff; + } + inline UBool checkInc(UChar c) { + if (p < e && (ch == c || *p == c)) { + inc(); + return TRUE; + } + return FALSE; + } + inline UBool check(UChar c) { + return p < e && (ch == c || *p == c); + } + inline void skipWhitespace(void) { + while (p < e && PatternProps::isWhiteSpace(ch != 0xffff ? ch : *p)) { + inc(); + } + } + inline UBool inList(UChar c, const UChar* list) const { + if (*list == SPACE && PatternProps::isWhiteSpace(c)) { + return TRUE; + } + while (*list && *list != c) { + ++list; + } + return *list == c; } void parseError(const char* msg); @@ -326,10 +352,12 @@ private: UChar* nextString(void); }; -#ifdef DEBUG +#ifdef RBNF_DEBUG #define ERROR(msg) parseError(msg); return NULL; +#define EXPLANATION_ARG explanationArg #else #define ERROR(msg) parseError(NULL); return NULL; +#define EXPLANATION_ARG #endif @@ -527,8 +555,8 @@ LocDataParser::nextString() { return result; } -void -LocDataParser::parseError(const char* /*str*/) { +void LocDataParser::parseError(const char* EXPLANATION_ARG) +{ if (!data) { return; } @@ -553,14 +581,14 @@ LocDataParser::parseError(const char* /*str*/) { pe.postContext[limit-p] = 0; pe.offset = (int32_t)(p - data); -#ifdef DEBUG - fprintf(stderr, "%s at or near character %d: ", str, p-data); +#ifdef RBNF_DEBUG + fprintf(stderr, "%s at or near character %ld: ", EXPLANATION_ARG, p-data); UnicodeString msg; msg.append(start, p - start); msg.append((UChar)0x002f); /* SOLIDUS/SLASH */ msg.append(p, limit-p); - msg.append("'"); + msg.append(UNICODE_STRING_SIMPLE("'")); char buf[128]; int32_t len = msg.extract(0, msg.length(), buf, 128); @@ -651,14 +679,22 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description, const UnicodeString& locs, const Locale& alocale, UParseError& perror, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(alocale) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) - , noParse(FALSE) //TODO: to be removed after #6895 + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status); init(description, locinfo, perror, status); @@ -668,14 +704,22 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description, const UnicodeString& locs, UParseError& perror, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(Locale::getDefault()) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) - , noParse(FALSE) //TODO: to be removed after #6895 + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status); init(description, locinfo, perror, status); @@ -685,14 +729,22 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description, LocalizationInfo* info, const Locale& alocale, UParseError& perror, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(alocale) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) - , noParse(FALSE) //TODO: to be removed after #6895 + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { init(description, info, perror, status); } @@ -701,14 +753,22 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description, UParseError& perror, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(Locale::getDefault()) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) - , noParse(FALSE) //TODO: to be removed after #6895 + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { init(description, NULL, perror, status); } @@ -718,27 +778,44 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const UnicodeString& description, UParseError& perror, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(aLocale) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) - , noParse(FALSE) //TODO: to be removed after #6895 + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { init(description, NULL, perror, status); } RuleBasedNumberFormat::RuleBasedNumberFormat(URBNFRuleSetTag tag, const Locale& alocale, UErrorCode& status) : ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(alocale) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { if (U_FAILURE(status)) { return; @@ -757,7 +834,6 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(URBNFRuleSetTag tag, const Locale& // TODO: read localization info from resource LocalizationInfo* locinfo = NULL; - int32_t len = 0; UResourceBundle* nfrb = ures_open(U_ICUDATA_RBNF, locale.getName(), &status); if (U_SUCCESS(status)) { setLocaleIDs(ures_getLocaleByType(nfrb, ULOC_VALID_LOCALE, &status), @@ -773,29 +849,14 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(URBNFRuleSetTag tag, const Locale& ures_close(nfrb); return; } - + UnicodeString desc; while (ures_hasNext(ruleSets)) { - const UChar* currentString = ures_getNextString(ruleSets,&len,NULL,&status); - desc.append(currentString); + desc.append(ures_getNextUnicodeString(ruleSets,NULL,&status)); } UParseError perror; - - init (desc, locinfo, perror, status); - - //TODO: we need a real fix - see #6895 / #6896 - noParse = FALSE; - if (tag == URBNF_SPELLOUT) { - const char *lang = alocale.getLanguage(); - for (int32_t i = 0; NO_SPELLOUT_PARSE_LANGUAGES[i] != NULL; i++) { - if (uprv_strcmp(lang, NO_SPELLOUT_PARSE_LANGUAGES[i]) == 0) { - noParse = TRUE; - break; - } - } - } - //TODO: end + init(desc, locinfo, perror, status); ures_close(ruleSets); ures_close(rbnfRules); @@ -806,13 +867,22 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(URBNFRuleSetTag tag, const Locale& RuleBasedNumberFormat::RuleBasedNumberFormat(const RuleBasedNumberFormat& rhs) : NumberFormat(rhs) , ruleSets(NULL) + , ruleSetDescriptions(NULL) + , numRuleSets(0) , defaultRuleSet(NULL) , locale(rhs.locale) , collator(NULL) , decimalFormatSymbols(NULL) + , defaultInfinityRule(NULL) + , defaultNaNRule(NULL) + , roundingMode(DecimalFormat::ERoundingMode::kRoundUnnecessary) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { this->operator=(rhs); } @@ -822,17 +892,27 @@ RuleBasedNumberFormat::RuleBasedNumberFormat(const RuleBasedNumberFormat& rhs) RuleBasedNumberFormat& RuleBasedNumberFormat::operator=(const RuleBasedNumberFormat& rhs) { + if (this == &rhs) { + return *this; + } + NumberFormat::operator=(rhs); UErrorCode status = U_ZERO_ERROR; dispose(); locale = rhs.locale; lenient = rhs.lenient; - UnicodeString rules = rhs.getRules(); UParseError perror; - init(rules, rhs.localizations ? rhs.localizations->ref() : NULL, perror, status); - - //TODO: remove below when we fix the parse bug - See #6895 / #6896 - noParse = rhs.noParse; + setDecimalFormatSymbols(*rhs.getDecimalFormatSymbols()); + init(rhs.originalDescription, rhs.localizations ? rhs.localizations->ref() : NULL, perror, status); + setDefaultRuleSet(rhs.getDefaultRuleSetName(), status); + setRoundingMode(rhs.getRoundingMode()); + + capitalizationInfoSet = rhs.capitalizationInfoSet; + capitalizationForUIListMenu = rhs.capitalizationForUIListMenu; + capitalizationForStandAlone = rhs.capitalizationForStandAlone; +#if !UCONFIG_NO_BREAK_ITERATION + capitalizationBrkIter = (rhs.capitalizationBrkIter!=NULL)? rhs.capitalizationBrkIter->clone(): NULL; +#endif return *this; } @@ -845,26 +925,7 @@ RuleBasedNumberFormat::~RuleBasedNumberFormat() Format* RuleBasedNumberFormat::clone(void) const { - RuleBasedNumberFormat * result = NULL; - UnicodeString rules = getRules(); - UErrorCode status = U_ZERO_ERROR; - UParseError perror; - result = new RuleBasedNumberFormat(rules, localizations, locale, perror, status); - /* test for NULL */ - if (result == 0) { - status = U_MEMORY_ALLOCATION_ERROR; - return 0; - } - if (U_FAILURE(status)) { - delete result; - result = 0; - } else { - result->lenient = lenient; - - //TODO: remove below when we fix the parse bug - See #6895 / #6896 - result->noParse = noParse; - } - return result; + return new RuleBasedNumberFormat(*this); } UBool @@ -876,6 +937,9 @@ RuleBasedNumberFormat::operator==(const Format& other) const if (typeid(*this) == typeid(other)) { const RuleBasedNumberFormat& rhs = (const RuleBasedNumberFormat&)other; + // test for capitalization info equality is adequately handled + // by the NumberFormat test for fCapitalizationContext equality; + // the info here is just derived from that. if (locale == rhs.locale && lenient == rhs.lenient && (localizations == NULL @@ -918,9 +982,10 @@ UnicodeString RuleBasedNumberFormat::getRuleSetName(int32_t index) const { if (localizations) { - UnicodeString string(TRUE, localizations->getRuleSetName(index), (int32_t)-1); - return string; - } else if (ruleSets) { + UnicodeString string(TRUE, localizations->getRuleSetName(index), (int32_t)-1); + return string; + } + else if (ruleSets) { UnicodeString result; for (NFRuleSet** p = ruleSets; *p; ++p) { NFRuleSet* rs = *p; @@ -941,8 +1006,9 @@ RuleBasedNumberFormat::getNumberOfRuleSetNames() const { int32_t result = 0; if (localizations) { - result = localizations->getNumberOfRuleSets(); - } else if (ruleSets) { + result = localizations->getNumberOfRuleSets(); + } + else if (ruleSets) { for (NFRuleSet** p = ruleSets; *p; ++p) { if ((**p).isPublic()) { ++result; @@ -1042,13 +1108,77 @@ RuleBasedNumberFormat::findRuleSet(const UnicodeString& name, UErrorCode& status return NULL; } +UnicodeString& +RuleBasedNumberFormat::format(const DigitList &number, + UnicodeString &appendTo, + FieldPositionIterator *posIter, + UErrorCode &status) const { + if (U_FAILURE(status)) { + return appendTo; + } + DigitList copy(number); + if (copy.fitsIntoInt64(false)) { + format(((DigitList &)number).getInt64(), appendTo, posIter, status); + } + else { + copy.roundAtExponent(0); + if (copy.fitsIntoInt64(false)) { + format(number.getDouble(), appendTo, posIter, status); + } + else { + // We're outside of our normal range that this framework can handle. + // The DecimalFormat will provide more accurate results. + + // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. + NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); + Formattable f; + f.adoptDigitList(new DigitList(number)); + decimalFormat->format(f, appendTo, posIter, status); + delete decimalFormat; + } + } + return appendTo; +} + + +UnicodeString& +RuleBasedNumberFormat::format(const DigitList &number, + UnicodeString& appendTo, + FieldPosition& pos, + UErrorCode &status) const { + if (U_FAILURE(status)) { + return appendTo; + } + DigitList copy(number); + if (copy.fitsIntoInt64(false)) { + format(((DigitList &)number).getInt64(), appendTo, pos, status); + } + else { + copy.roundAtExponent(0); + if (copy.fitsIntoInt64(false)) { + format(number.getDouble(), appendTo, pos, status); + } + else { + // We're outside of our normal range that this framework can handle. + // The DecimalFormat will provide more accurate results. + + // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. + NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); + Formattable f; + f.adoptDigitList(new DigitList(number)); + decimalFormat->format(f, appendTo, pos, status); + delete decimalFormat; + } + } + return appendTo; +} + UnicodeString& RuleBasedNumberFormat::format(int32_t number, UnicodeString& toAppendTo, - FieldPosition& /* pos */) const + FieldPosition& pos) const { - if (defaultRuleSet) defaultRuleSet->format((int64_t)number, toAppendTo, toAppendTo.length()); - return toAppendTo; + return format((int64_t)number, toAppendTo, pos); } @@ -1057,7 +1187,10 @@ RuleBasedNumberFormat::format(int64_t number, UnicodeString& toAppendTo, FieldPosition& /* pos */) const { - if (defaultRuleSet) defaultRuleSet->format(number, toAppendTo, toAppendTo.length()); + if (defaultRuleSet) { + UErrorCode status = U_ZERO_ERROR; + format(number, defaultRuleSet, toAppendTo, status); + } return toAppendTo; } @@ -1067,14 +1200,9 @@ RuleBasedNumberFormat::format(double number, UnicodeString& toAppendTo, FieldPosition& /* pos */) const { - // Special case for NaN; adapted from what DecimalFormat::_format( double number,...) does. - if (uprv_isNaN(number)) { - DecimalFormatSymbols* decFmtSyms = getDecimalFormatSymbols(); // RuleBasedNumberFormat internal - if (decFmtSyms) { - toAppendTo += decFmtSyms->getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - } - } else if (defaultRuleSet) { - defaultRuleSet->format(number, toAppendTo, toAppendTo.length()); + UErrorCode status = U_ZERO_ERROR; + if (defaultRuleSet) { + format(number, *defaultRuleSet, toAppendTo, status); } return toAppendTo; } @@ -1082,20 +1210,30 @@ RuleBasedNumberFormat::format(double number, UnicodeString& RuleBasedNumberFormat::format(int32_t number, + const UnicodeString& ruleSetName, + UnicodeString& toAppendTo, + FieldPosition& pos, + UErrorCode& status) const +{ + return format((int64_t)number, ruleSetName, toAppendTo, pos, status); +} + + +UnicodeString& +RuleBasedNumberFormat::format(int64_t number, const UnicodeString& ruleSetName, UnicodeString& toAppendTo, FieldPosition& /* pos */, UErrorCode& status) const { - // return format((int64_t)number, ruleSetName, toAppendTo, pos, status); if (U_SUCCESS(status)) { - if (ruleSetName.indexOf(gPercentPercent) == 0) { + if (ruleSetName.indexOf(gPercentPercent, 2, 0) == 0) { // throw new IllegalArgumentException("Can't use internal rule set"); status = U_ILLEGAL_ARGUMENT_ERROR; } else { NFRuleSet *rs = findRuleSet(ruleSetName, status); if (rs) { - rs->format((int64_t)number, toAppendTo, toAppendTo.length()); + format(number, rs, toAppendTo, status); } } } @@ -1104,70 +1242,118 @@ RuleBasedNumberFormat::format(int32_t number, UnicodeString& -RuleBasedNumberFormat::format(int64_t number, +RuleBasedNumberFormat::format(double number, const UnicodeString& ruleSetName, UnicodeString& toAppendTo, FieldPosition& /* pos */, UErrorCode& status) const { if (U_SUCCESS(status)) { - if (ruleSetName.indexOf(gPercentPercent) == 0) { + if (ruleSetName.indexOf(gPercentPercent, 2, 0) == 0) { // throw new IllegalArgumentException("Can't use internal rule set"); status = U_ILLEGAL_ARGUMENT_ERROR; } else { NFRuleSet *rs = findRuleSet(ruleSetName, status); if (rs) { - rs->format(number, toAppendTo, toAppendTo.length()); + format(number, *rs, toAppendTo, status); } } } return toAppendTo; } - -// make linker happy -UnicodeString& -RuleBasedNumberFormat::format(const Formattable& obj, +void +RuleBasedNumberFormat::format(double number, + NFRuleSet& rs, UnicodeString& toAppendTo, - FieldPosition& pos, UErrorCode& status) const { - return NumberFormat::format(obj, toAppendTo, pos, status); + int32_t startPos = toAppendTo.length(); + if (getRoundingMode() != DecimalFormat::ERoundingMode::kRoundUnnecessary && !uprv_isNaN(number) && !uprv_isInfinite(number)) { + DigitList digitList; + digitList.set(number); + digitList.setRoundingMode(getRoundingMode()); + digitList.roundFixedPoint(getMaximumFractionDigits()); + number = digitList.getDouble(); + } + rs.format(number, toAppendTo, toAppendTo.length(), 0, status); + adjustForCapitalizationContext(startPos, toAppendTo, status); } +/** + * Bottleneck through which all the public format() methods + * that take a long pass. By the time we get here, we know + * which rule set we're using to do the formatting. + * @param number The number to format + * @param ruleSet The rule set to use to format the number + * @return The text that resulted from formatting the number + */ UnicodeString& -RuleBasedNumberFormat::format(double number, - const UnicodeString& ruleSetName, - UnicodeString& toAppendTo, - FieldPosition& /* pos */, - UErrorCode& status) const +RuleBasedNumberFormat::format(int64_t number, NFRuleSet *ruleSet, UnicodeString& toAppendTo, UErrorCode& status) const { + // all API format() routines that take a double vector through + // here. We have these two identical functions-- one taking a + // double and one taking a long-- the couple digits of precision + // that long has but double doesn't (both types are 8 bytes long, + // but double has to borrow some of the mantissa bits to hold + // the exponent). + // Create an empty string buffer where the result will + // be built, and pass it to the rule set (along with an insertion + // position of 0 and the number being formatted) to the rule set + // for formatting + if (U_SUCCESS(status)) { - if (ruleSetName.indexOf(gPercentPercent) == 0) { - // throw new IllegalArgumentException("Can't use internal rule set"); - status = U_ILLEGAL_ARGUMENT_ERROR; - } else { - NFRuleSet *rs = findRuleSet(ruleSetName, status); - if (rs) { - rs->format(number, toAppendTo, toAppendTo.length()); - } + if (number == U_INT64_MIN) { + // We can't handle this value right now. Provide an accurate default value. + + // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. + NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); + Formattable f; + FieldPosition pos(FieldPosition::DONT_CARE); + DigitList *digitList = new DigitList(); + digitList->set(number); + f.adoptDigitList(digitList); + decimalFormat->format(f, toAppendTo, pos, status); + delete decimalFormat; + } + else { + int32_t startPos = toAppendTo.length(); + ruleSet->format(number, toAppendTo, toAppendTo.length(), 0, status); + adjustForCapitalizationContext(startPos, toAppendTo, status); } } return toAppendTo; } +UnicodeString& +RuleBasedNumberFormat::adjustForCapitalizationContext(int32_t startPos, + UnicodeString& currentResult, + UErrorCode& status) const +{ +#if !UCONFIG_NO_BREAK_ITERATION + UDisplayContext capitalizationContext = getContext(UDISPCTX_TYPE_CAPITALIZATION, status); + if (capitalizationContext != UDISPCTX_CAPITALIZATION_NONE && startPos == 0 && currentResult.length() > 0) { + // capitalize currentResult according to context + UChar32 ch = currentResult.char32At(0); + if (u_islower(ch) && U_SUCCESS(status) && capitalizationBrkIter != NULL && + ( capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || + (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationForUIListMenu) || + (capitalizationContext == UDISPCTX_CAPITALIZATION_FOR_STANDALONE && capitalizationForStandAlone)) ) { + // titlecase first word of currentResult, here use sentence iterator unlike current implementations + // in LocaleDisplayNamesImpl::adjustForUsageAndContext and RelativeDateFormat::format + currentResult.toTitle(capitalizationBrkIter, locale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); + } + } +#endif + return currentResult; +} + + void RuleBasedNumberFormat::parse(const UnicodeString& text, Formattable& result, ParsePosition& parsePosition) const { - //TODO: We need a real fix. See #6895 / #6896 - if (noParse) { - // skip parsing - parsePosition.setErrorIndex(0); - return; - } - if (!ruleSets) { parsePosition.setErrorIndex(0); return; @@ -1185,7 +1371,7 @@ RuleBasedNumberFormat::parse(const UnicodeString& text, ParsePosition working_pp(0); Formattable working_result; - rp->parse(workingText, working_pp, kMaxDouble, working_result, lenient); + rp->parse(workingText, working_pp, kMaxDouble, 0, working_result, lenient); if (working_pp.getIndex() > high_pp.getIndex()) { high_pp = working_pp; high_result = working_result; @@ -1207,9 +1393,12 @@ RuleBasedNumberFormat::parse(const UnicodeString& text, } result = high_result; if (result.getType() == Formattable::kDouble) { - int32_t r = (int32_t)result.getDouble(); - if ((double)r == result.getDouble()) { - result.setLong(r); + double d = result.getDouble(); + if (!uprv_isNaN(d) && d == uprv_trunc(d) && INT32_MIN <= d && d <= INT32_MAX) { + // Note: casting a double to an int when the double is too large or small + // to fit the destination is undefined behavior. The explicit range checks, + // above, are required. Just casting and checking the result value is undefined. + result.setLong(static_cast(d)); } } } @@ -1251,13 +1440,13 @@ RuleBasedNumberFormat::setDefaultRuleSet(const UnicodeString& ruleSetName, UErro UnicodeString RuleBasedNumberFormat::getDefaultRuleSetName() const { - UnicodeString result; - if (defaultRuleSet && defaultRuleSet->isPublic()) { - defaultRuleSet->getName(result); - } else { - result.setToBogus(); - } - return result; + UnicodeString result; + if (defaultRuleSet && defaultRuleSet->isPublic()) { + defaultRuleSet->getName(result); + } else { + result.setToBogus(); + } + return result; } void @@ -1265,12 +1454,12 @@ RuleBasedNumberFormat::initDefaultRuleSet() { defaultRuleSet = NULL; if (!ruleSets) { - return; + return; } - const UnicodeString spellout = UNICODE_STRING_SIMPLE("%spellout-numbering"); - const UnicodeString ordinal = UNICODE_STRING_SIMPLE("%digits-ordinal"); - const UnicodeString duration = UNICODE_STRING_SIMPLE("%duration"); + const UnicodeString spellout(UNICODE_STRING_SIMPLE("%spellout-numbering")); + const UnicodeString ordinal(UNICODE_STRING_SIMPLE("%digits-ordinal")); + const UnicodeString duration(UNICODE_STRING_SIMPLE("%duration")); NFRuleSet**p = &ruleSets[0]; while (*p) { @@ -1305,6 +1494,13 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali return; } + initializeDecimalFormatSymbols(status); + initializeDefaultInfinityRule(status); + initializeDefaultNaNRule(status); + if (U_FAILURE(status)) { + return; + } + this->localizations = localizationInfos == NULL ? NULL : localizationInfos->ref(); UnicodeString description(rules); @@ -1324,7 +1520,7 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali // is, pull them out into our temporary holding place for them, // and delete them from the description before the real desciption- // parsing code sees them - int32_t lp = description.indexOf(gLenientParse); + int32_t lp = description.indexOf(gLenientParse, -1, 0); if (lp != -1) { // we've got to make sure we're not in the middle of a rule // (where "%%lenient-parse" would actually get treated as @@ -1333,13 +1529,13 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali // locate the beginning and end of the actual collation // rules (there may be whitespace between the name and // the first token in the description) - int lpEnd = description.indexOf(gSemiPercent, lp); + int lpEnd = description.indexOf(gSemiPercent, 2, lp); if (lpEnd == -1) { lpEnd = description.length() - 1; } int lpStart = lp + u_strlen(gLenientParse); - while (uprv_isRuleWhiteSpace(description.charAt(lpStart))) { + while (PatternProps::isWhiteSpace(description.charAt(lpStart))) { ++lpStart; } @@ -1360,8 +1556,8 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali // pre-flight parsing the description and count the number of // rule sets (";%" marks the end of one rule set and the beginning // of the next) - int numRuleSets = 0; - for (int32_t p = description.indexOf(gSemiPercent); p != -1; p = description.indexOf(gSemiPercent, p)) { + numRuleSets = 0; + for (int32_t p = description.indexOf(gSemiPercent, 2, 0); p != -1; p = description.indexOf(gSemiPercent, 2, p)) { ++numRuleSets; ++p; } @@ -1390,7 +1586,8 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali status = U_ILLEGAL_ARGUMENT_ERROR; return; } - UnicodeString* ruleSetDescriptions = new UnicodeString[numRuleSets]; + + ruleSetDescriptions = new UnicodeString[numRuleSets]; if (ruleSetDescriptions == 0) { status = U_MEMORY_ALLOCATION_ERROR; return; @@ -1399,21 +1596,21 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali { int curRuleSet = 0; int32_t start = 0; - for (int32_t p = description.indexOf(gSemiPercent); p != -1; p = description.indexOf(gSemiPercent, start)) { + for (int32_t p = description.indexOf(gSemiPercent, 2, 0); p != -1; p = description.indexOf(gSemiPercent, 2, start)) { ruleSetDescriptions[curRuleSet].setTo(description, start, p + 1 - start); - ruleSets[curRuleSet] = new NFRuleSet(ruleSetDescriptions, curRuleSet, status); + ruleSets[curRuleSet] = new NFRuleSet(this, ruleSetDescriptions, curRuleSet, status); if (ruleSets[curRuleSet] == 0) { status = U_MEMORY_ALLOCATION_ERROR; - goto cleanup; + return; } ++curRuleSet; start = p + 1; } ruleSetDescriptions[curRuleSet].setTo(description, start, description.length() - start); - ruleSets[curRuleSet] = new NFRuleSet(ruleSetDescriptions, curRuleSet, status); + ruleSets[curRuleSet] = new NFRuleSet(this, ruleSetDescriptions, curRuleSet, status); if (ruleSets[curRuleSet] == 0) { status = U_MEMORY_ALLOCATION_ERROR; - goto cleanup; + return; } } @@ -1433,7 +1630,7 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali // away the temporary descriptions as we go) { for (int i = 0; i < numRuleSets; i++) { - ruleSets[i]->parseRules(ruleSetDescriptions[i], this, status); + ruleSets[i]->parseRules(ruleSetDescriptions[i], status); } } @@ -1460,9 +1657,55 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali } else { defaultRuleSet = getDefaultRuleSet(); } + originalDescription = rules; +} -cleanup: - delete[] ruleSetDescriptions; +// override the NumberFormat implementation in order to +// lazily initialize relevant items +void +RuleBasedNumberFormat::setContext(UDisplayContext value, UErrorCode& status) +{ + NumberFormat::setContext(value, status); + if (U_SUCCESS(status)) { + if (!capitalizationInfoSet && + (value==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU || value==UDISPCTX_CAPITALIZATION_FOR_STANDALONE)) { + initCapitalizationContextInfo(locale); + capitalizationInfoSet = TRUE; + } +#if !UCONFIG_NO_BREAK_ITERATION + if ( capitalizationBrkIter == NULL && (value==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE || + (value==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU && capitalizationForUIListMenu) || + (value==UDISPCTX_CAPITALIZATION_FOR_STANDALONE && capitalizationForStandAlone)) ) { + UErrorCode status = U_ZERO_ERROR; + capitalizationBrkIter = BreakIterator::createSentenceInstance(locale, status); + if (U_FAILURE(status)) { + delete capitalizationBrkIter; + capitalizationBrkIter = NULL; + } + } +#endif + } +} + +void +RuleBasedNumberFormat::initCapitalizationContextInfo(const Locale& thelocale) +{ +#if !UCONFIG_NO_BREAK_ITERATION + const char * localeID = (thelocale != NULL)? thelocale.getBaseName(): NULL; + UErrorCode status = U_ZERO_ERROR; + UResourceBundle *rb = ures_open(NULL, localeID, &status); + rb = ures_getByKeyWithFallback(rb, "contextTransforms", rb, &status); + rb = ures_getByKeyWithFallback(rb, "number-spellout", rb, &status); + if (U_SUCCESS(status) && rb != NULL) { + int32_t len = 0; + const int32_t * intVector = ures_getIntVector(rb, &len, &status); + if (U_SUCCESS(status) && intVector != NULL && len >= 2) { + capitalizationForUIListMenu = intVector[0]; + capitalizationForStandAlone = intVector[1]; + } + } + ures_close(rb); +#endif } void @@ -1475,7 +1718,7 @@ RuleBasedNumberFormat::stripWhitespace(UnicodeString& description) while (start != -1 && start < description.length()) { // seek to the first non-whitespace character... while (start < description.length() - && uprv_isRuleWhiteSpace(description.charAt(start))) { + && PatternProps::isWhiteSpace(description.charAt(start))) { ++start; } @@ -1517,6 +1760,11 @@ RuleBasedNumberFormat::dispose() ruleSets = NULL; } + if (ruleSetDescriptions) { + delete [] ruleSetDescriptions; + ruleSetDescriptions = NULL; + } + #if !UCONFIG_NO_COLLATION delete collator; #endif @@ -1525,10 +1773,23 @@ RuleBasedNumberFormat::dispose() delete decimalFormatSymbols; decimalFormatSymbols = NULL; + delete defaultInfinityRule; + defaultInfinityRule = NULL; + + delete defaultNaNRule; + defaultNaNRule = NULL; + delete lenientParseRules; lenientParseRules = NULL; - if (localizations) localizations = localizations->unref(); +#if !UCONFIG_NO_BREAK_ITERATION + delete capitalizationBrkIter; + capitalizationBrkIter = NULL; +#endif + + if (localizations) { + localizations = localizations->unref(); + } } @@ -1542,7 +1803,7 @@ RuleBasedNumberFormat::dispose() * @return The collator to use for lenient parsing, or null if lenient parsing * is turned off. */ -Collator* +const RuleBasedCollator* RuleBasedNumberFormat::getCollator() const { #if !UCONFIG_NO_COLLATION @@ -1550,7 +1811,7 @@ RuleBasedNumberFormat::getCollator() const return NULL; } - // lazy-evaulate the collator + // lazy-evaluate the collator if (collator == NULL && lenient) { // create a default collator based on the formatter's locale, // then pull out that collator's rules, append any additional @@ -1569,7 +1830,7 @@ RuleBasedNumberFormat::getCollator() const newCollator = new RuleBasedCollator(rules, status); // Exit if newCollator could not be created. if (newCollator == NULL) { - return NULL; + return NULL; } } else { temp = NULL; @@ -1592,31 +1853,152 @@ RuleBasedNumberFormat::getCollator() const } -/** - * Returns the DecimalFormatSymbols object that should be used by all DecimalFormat - * instances owned by this formatter. This object is lazily created: this function - * creates it the first time it's called. - * @return The DecimalFormatSymbols object that should be used by all DecimalFormat - * instances owned by this formatter. -*/ DecimalFormatSymbols* -RuleBasedNumberFormat::getDecimalFormatSymbols() const +RuleBasedNumberFormat::initializeDecimalFormatSymbols(UErrorCode &status) { // lazy-evaluate the DecimalFormatSymbols object. This object // is shared by all DecimalFormat instances belonging to this // formatter if (decimalFormatSymbols == NULL) { - UErrorCode status = U_ZERO_ERROR; DecimalFormatSymbols* temp = new DecimalFormatSymbols(locale, status); if (U_SUCCESS(status)) { - ((RuleBasedNumberFormat*)this)->decimalFormatSymbols = temp; - } else { + decimalFormatSymbols = temp; + } + else { delete temp; } } return decimalFormatSymbols; } +/** + * Returns the DecimalFormatSymbols object that should be used by all DecimalFormat + * instances owned by this formatter. +*/ +const DecimalFormatSymbols* +RuleBasedNumberFormat::getDecimalFormatSymbols() const +{ + return decimalFormatSymbols; +} + +NFRule* +RuleBasedNumberFormat::initializeDefaultInfinityRule(UErrorCode &status) +{ + if (U_FAILURE(status)) { + return NULL; + } + if (defaultInfinityRule == NULL) { + UnicodeString rule(UNICODE_STRING_SIMPLE("Inf: ")); + rule.append(getDecimalFormatSymbols()->getSymbol(DecimalFormatSymbols::kInfinitySymbol)); + NFRule* temp = new NFRule(this, rule, status); + if (U_SUCCESS(status)) { + defaultInfinityRule = temp; + } + else { + delete temp; + } + } + return defaultInfinityRule; +} + +const NFRule* +RuleBasedNumberFormat::getDefaultInfinityRule() const +{ + return defaultInfinityRule; +} + +NFRule* +RuleBasedNumberFormat::initializeDefaultNaNRule(UErrorCode &status) +{ + if (U_FAILURE(status)) { + return NULL; + } + if (defaultNaNRule == NULL) { + UnicodeString rule(UNICODE_STRING_SIMPLE("NaN: ")); + rule.append(getDecimalFormatSymbols()->getSymbol(DecimalFormatSymbols::kNaNSymbol)); + NFRule* temp = new NFRule(this, rule, status); + if (U_SUCCESS(status)) { + defaultNaNRule = temp; + } + else { + delete temp; + } + } + return defaultNaNRule; +} + +const NFRule* +RuleBasedNumberFormat::getDefaultNaNRule() const +{ + return defaultNaNRule; +} + +// De-owning the current localized symbols and adopt the new symbols. +void +RuleBasedNumberFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) +{ + if (symbolsToAdopt == NULL) { + return; // do not allow caller to set decimalFormatSymbols to NULL + } + + if (decimalFormatSymbols != NULL) { + delete decimalFormatSymbols; + } + + decimalFormatSymbols = symbolsToAdopt; + + { + // Apply the new decimalFormatSymbols by reparsing the rulesets + UErrorCode status = U_ZERO_ERROR; + + delete defaultInfinityRule; + defaultInfinityRule = NULL; + initializeDefaultInfinityRule(status); // Reset with the new DecimalFormatSymbols + + delete defaultNaNRule; + defaultNaNRule = NULL; + initializeDefaultNaNRule(status); // Reset with the new DecimalFormatSymbols + + if (ruleSets) { + for (int32_t i = 0; i < numRuleSets; i++) { + ruleSets[i]->setDecimalFormatSymbols(*symbolsToAdopt, status); + } + } + } +} + +// Setting the symbols is equlivalent to adopting a newly created localized symbols. +void +RuleBasedNumberFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) +{ + adoptDecimalFormatSymbols(new DecimalFormatSymbols(symbols)); +} + +PluralFormat * +RuleBasedNumberFormat::createPluralFormat(UPluralType pluralType, + const UnicodeString &pattern, + UErrorCode& status) const +{ + return new PluralFormat(locale, pluralType, pattern, status); +} + +/** + * Get the rounding mode. + * @return A rounding mode + */ +DecimalFormat::ERoundingMode RuleBasedNumberFormat::getRoundingMode() const { + return roundingMode; +} + +/** + * Set the rounding mode. This has no effect unless the rounding + * increment is greater than zero. + * @param roundingMode A rounding mode + */ +void RuleBasedNumberFormat::setRoundingMode(DecimalFormat::ERoundingMode roundingMode) { + this->roundingMode = roundingMode; +} + U_NAMESPACE_END /* U_HAVE_RBNF */