X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/374ca955a76ecab1204ca8bfa63ff9238d998416..38fbf2fd31f5cd99b500914d6037b1d06b608645:/icuSources/i18n/rbnf.cpp?ds=inline diff --git a/icuSources/i18n/rbnf.cpp b/icuSources/i18n/rbnf.cpp index ad2d71b6..a182c71e 100644 --- a/icuSources/i18n/rbnf.cpp +++ b/icuSources/i18n/rbnf.cpp @@ -1,14 +1,21 @@ +// © 2016 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html /* ******************************************************************************* -* Copyright (C) 1997-2004, International Business Machines Corporation and others. All Rights Reserved. +* Copyright (C) 1997-2015, International Business Machines Corporation +* and others. All Rights Reserved. ******************************************************************************* */ +#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" @@ -18,17 +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,9 +70,16 @@ U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedNumberFormat) -class LocalizationInfo : public UObject { +/* +This is a utility class. It does not use ICU's RTTI. +If ICU's RTTI is needed again, you can uncomment the RTTI code and derive from UObject. +Please make sure that intltest passes on Windows in Release mode, +since the string pooling per compilation unit will mess up how RTTI works. +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: @@ -90,11 +109,13 @@ public: virtual int32_t indexForLocale(const UChar* locale) const; virtual int32_t indexForRuleSet(const UChar* ruleset) const; - virtual UClassID getDynamicClassID() const = 0; - static UClassID getStaticClassID(void); +// virtual UClassID getDynamicClassID() const = 0; +// static UClassID getStaticClassID(void); }; -UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(LocalizationInfo) +LocalizationInfo::~LocalizationInfo() {} + +//UOBJECT_DEFINE_ABSTRACT_RTTI_IMPLEMENTATION(LocalizationInfo) // if both strings are NULL, this returns TRUE static UBool @@ -254,8 +275,8 @@ public: virtual const UChar* getLocaleName(int32_t index) const; virtual const UChar* getDisplayName(int32_t localeIndex, int32_t ruleIndex) const; - virtual UClassID getDynamicClassID() const; - static UClassID getStaticClassID(void); +// virtual UClassID getDynamicClassID() const; +// static UClassID getStaticClassID(void); private: void init(UErrorCode& status) const; @@ -298,9 +319,9 @@ 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();} + void skipWhitespace(void) { while (p < e && PatternProps::isWhiteSpace(ch != 0xffff ? ch : *p)) inc();} UBool inList(UChar c, const UChar* list) const { - if (*list == SPACE && uprv_isRuleWhiteSpace(c)) return TRUE; + if (*list == SPACE && PatternProps::isWhiteSpace(c)) return TRUE; while (*list && *list != c) ++list; return *list == c; } void parseError(const char* msg); @@ -311,10 +332,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 @@ -512,37 +535,40 @@ LocDataParser::nextString() { return result; } -void -LocDataParser::parseError(const char* /*str*/) { +void LocDataParser::parseError(const char* EXPLANATION_ARG) +{ if (!data) { return; } const UChar* start = p - U_PARSE_CONTEXT_LEN - 1; - if (start < data) + if (start < data) { start = data; - for (UChar* x = p; --x >= start;) + } + for (UChar* x = p; --x >= start;) { if (!*x) { start = x+1; break; } + } const UChar* limit = p + U_PARSE_CONTEXT_LEN - 1; - if (limit > e) + if (limit > e) { limit = e; - u_strncpy(pe.preContext, start, p-start); + } + u_strncpy(pe.preContext, start, (int32_t)(p-start)); pe.preContext[p-start] = 0; - u_strncpy(pe.postContext, p, limit-p); + u_strncpy(pe.postContext, p, (int32_t)(limit-p)); pe.postContext[limit-p] = 0; - pe.offset = p - data; + 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); @@ -565,7 +591,7 @@ LocDataParser::parseError(const char* /*str*/) { } } -UOBJECT_DEFINE_RTTI_IMPLEMENTATION(StringLocalizationInfo) +//UOBJECT_DEFINE_RTTI_IMPLEMENTATION(StringLocalizationInfo) StringLocalizationInfo* StringLocalizationInfo::create(const UnicodeString& info, UParseError& perror, UErrorCode& status) { @@ -633,13 +659,21 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status); init(description, locinfo, perror, status); @@ -649,13 +683,21 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { LocalizationInfo* locinfo = StringLocalizationInfo::create(locs, perror, status); init(description, locinfo, perror, status); @@ -665,13 +707,21 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { init(description, info, perror, status); } @@ -680,13 +730,21 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { init(description, NULL, perror, status); } @@ -696,51 +754,86 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { if (U_FAILURE(status)) { return; } + const char* rules_tag = "RBNFRules"; const char* fmt_tag = ""; switch (tag) { case URBNF_SPELLOUT: fmt_tag = "SpelloutRules"; break; case URBNF_ORDINAL: fmt_tag = "OrdinalRules"; break; case URBNF_DURATION: fmt_tag = "DurationRules"; break; + case URBNF_NUMBERING_SYSTEM: fmt_tag = "NumberingSystemRules"; break; default: status = U_ILLEGAL_ARGUMENT_ERROR; return; } // 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), ures_getLocaleByType(nfrb, ULOC_ACTUAL_LOCALE, &status)); - const UChar* description = ures_getStringByKey(nfrb, fmt_tag, &len, &status); - UnicodeString desc(description, len); + + UResourceBundle* rbnfRules = ures_getByKeyWithFallback(nfrb, rules_tag, NULL, &status); + if (U_FAILURE(status)) { + ures_close(nfrb); + } + UResourceBundle* ruleSets = ures_getByKeyWithFallback(rbnfRules, fmt_tag, NULL, &status); + if (U_FAILURE(status)) { + ures_close(rbnfRules); + ures_close(nfrb); + return; + } + + UnicodeString desc; + while (ures_hasNext(ruleSets)) { + desc.append(ures_getNextUnicodeString(ruleSets,NULL,&status)); + } UParseError perror; - init (desc, locinfo, perror, status); + + init(desc, locinfo, perror, status); + + ures_close(ruleSets); + ures_close(rbnfRules); } ures_close(nfrb); } @@ -748,13 +841,21 @@ 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) , lenient(FALSE) , lenientParseRules(NULL) , localizations(NULL) + , capitalizationInfoSet(FALSE) + , capitalizationForUIListMenu(FALSE) + , capitalizationForStandAlone(FALSE) + , capitalizationBrkIter(NULL) { this->operator=(rhs); } @@ -764,14 +865,26 @@ 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); + setDecimalFormatSymbols(*rhs.getDecimalFormatSymbols()); + init(rhs.originalDescription, rhs.localizations ? rhs.localizations->ref() : NULL, perror, status); + setDefaultRuleSet(rhs.getDefaultRuleSetName(), status); + + 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; } @@ -784,23 +897,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; - } - return result; + return new RuleBasedNumberFormat(*this); } UBool @@ -810,8 +907,11 @@ RuleBasedNumberFormat::operator==(const Format& other) const return TRUE; } - if (other.getDynamicClassID() == getStaticClassID()) { + 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 @@ -854,9 +954,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; @@ -877,8 +978,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; @@ -899,7 +1001,7 @@ RuleBasedNumberFormat::getNumberOfRuleSetDisplayNameLocales(void) const { Locale RuleBasedNumberFormat::getRuleSetDisplayNameLocale(int32_t index, UErrorCode& status) const { if (U_FAILURE(status)) { - return Locale(); + return Locale(""); } if (localizations && index >= 0 && index < localizations->getNumberOfDisplayLocales()) { UnicodeString name(TRUE, localizations->getLocaleName(index), -1); @@ -910,7 +1012,7 @@ RuleBasedNumberFormat::getRuleSetDisplayNameLocale(int32_t index, UErrorCode& st bp = (char *)uprv_malloc(cap); if (bp == NULL) { status = U_MEMORY_ALLOCATION_ERROR; - return Locale(); + return Locale(""); } } name.extract(0, name.length(), bp, cap, UnicodeString::kInvariant); @@ -978,13 +1080,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); } @@ -993,7 +1159,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; } @@ -1003,8 +1172,12 @@ RuleBasedNumberFormat::format(double number, UnicodeString& toAppendTo, FieldPosition& /* pos */) const { - if (defaultRuleSet) defaultRuleSet->format(number, toAppendTo, toAppendTo.length()); - return toAppendTo; + int32_t startPos = toAppendTo.length(); + UErrorCode status = U_ZERO_ERROR; + if (defaultRuleSet) { + defaultRuleSet->format(number, toAppendTo, toAppendTo.length(), 0, status); + } + return adjustForCapitalizationContext(startPos, toAppendTo, status); } @@ -1012,22 +1185,10 @@ UnicodeString& RuleBasedNumberFormat::format(int32_t number, const UnicodeString& ruleSetName, UnicodeString& toAppendTo, - FieldPosition& /* pos */, + FieldPosition& pos, UErrorCode& status) const { - // return format((int64_t)number, ruleSetName, toAppendTo, pos, status); - 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((int64_t)number, toAppendTo, toAppendTo.length()); - } - } - } - return toAppendTo; + return format((int64_t)number, ruleSetName, toAppendTo, pos, status); } @@ -1039,13 +1200,13 @@ RuleBasedNumberFormat::format(int64_t number, 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); } } } @@ -1053,16 +1214,6 @@ RuleBasedNumberFormat::format(int64_t number, } -// make linker happy -UnicodeString& -RuleBasedNumberFormat::format(const Formattable& obj, - UnicodeString& toAppendTo, - FieldPosition& pos, - UErrorCode& status) const -{ - return NumberFormat::format(obj, toAppendTo, pos, status); -} - UnicodeString& RuleBasedNumberFormat::format(double number, const UnicodeString& ruleSetName, @@ -1071,19 +1222,90 @@ RuleBasedNumberFormat::format(double number, 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()); + int32_t startPos = toAppendTo.length(); + rs->format(number, toAppendTo, toAppendTo.length(), 0, status); + adjustForCapitalizationContext(startPos, toAppendTo, status); } } } return toAppendTo; } +/** + * 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(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 (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, @@ -1102,11 +1324,11 @@ RuleBasedNumberFormat::parse(const UnicodeString& text, for (NFRuleSet** p = ruleSets; *p; ++p) { NFRuleSet *rp = *p; - if (rp->isPublic()) { + if (rp->isPublic() && rp->isParseable()) { ParsePosition working_pp(0); Formattable working_result; - rp->parse(workingText, working_pp, kMaxDouble, working_result); + rp->parse(workingText, working_pp, kMaxDouble, working_result, lenient); if (working_pp.getIndex() > high_pp.getIndex()) { high_pp = working_pp; high_result = working_result; @@ -1118,15 +1340,22 @@ RuleBasedNumberFormat::parse(const UnicodeString& text, } } - parsePosition.setIndex(parsePosition.getIndex() + high_pp.getIndex()); + int32_t startIndex = parsePosition.getIndex(); + parsePosition.setIndex(startIndex + high_pp.getIndex()); if (high_pp.getIndex() > 0) { parsePosition.setErrorIndex(-1); + } else { + int32_t errorIndex = (high_pp.getErrorIndex()>0)? high_pp.getErrorIndex(): 0; + parsePosition.setErrorIndex(startIndex + errorIndex); } 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)); } } } @@ -1168,13 +1397,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 @@ -1182,12 +1411,21 @@ 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")); + NFRuleSet**p = &ruleSets[0]; while (*p) { - ++p; + if ((*p)->isNamed(spellout) || (*p)->isNamed(ordinal) || (*p)->isNamed(duration)) { + defaultRuleSet = *p; + return; + } else { + ++p; + } } defaultRuleSet = *--p; @@ -1204,14 +1442,22 @@ RuleBasedNumberFormat::initDefaultRuleSet() void RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* localizationInfos, - UParseError& /* pErr */, UErrorCode& status) + UParseError& pErr, UErrorCode& status) { // TODO: implement UParseError + uprv_memset(&pErr, 0, sizeof(UParseError)); // Note: this can leave ruleSets == NULL, so remaining code should check if (U_FAILURE(status)) { return; } + initializeDecimalFormatSymbols(status); + initializeDefaultInfinityRule(status); + initializeDefaultNaNRule(status); + if (U_FAILURE(status)) { + return; + } + this->localizations = localizationInfos == NULL ? NULL : localizationInfos->ref(); UnicodeString description(rules); @@ -1231,7 +1477,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 @@ -1240,13 +1486,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; } @@ -1267,8 +1513,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; } @@ -1294,11 +1540,11 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali // because we have to know the names and locations of all the rule // sets before we can actually set everything up if(!numRuleSets) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return; + status = U_ILLEGAL_ARGUMENT_ERROR; + return; } - UnicodeString* ruleSetDescriptions = new UnicodeString[numRuleSets]; - /* test for NULL */ + + ruleSetDescriptions = new UnicodeString[numRuleSets]; if (ruleSetDescriptions == 0) { status = U_MEMORY_ALLOCATION_ERROR; return; @@ -1307,10 +1553,9 @@ 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); - /* test for NULL */ + ruleSets[curRuleSet] = new NFRuleSet(this, ruleSetDescriptions, curRuleSet, status); if (ruleSets[curRuleSet] == 0) { status = U_MEMORY_ALLOCATION_ERROR; return; @@ -1319,8 +1564,7 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali start = p + 1; } ruleSetDescriptions[curRuleSet].setTo(description, start, description.length() - start); - ruleSets[curRuleSet] = new NFRuleSet(ruleSetDescriptions, curRuleSet, status); - /* test for NULL */ + ruleSets[curRuleSet] = new NFRuleSet(this, ruleSetDescriptions, curRuleSet, status); if (ruleSets[curRuleSet] == 0) { status = U_MEMORY_ALLOCATION_ERROR; return; @@ -1343,12 +1587,10 @@ 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); } } - delete[] ruleSetDescriptions; - // Now that the rules are initialized, the 'real' default rule // set can be adjusted by the localization data. @@ -1372,6 +1614,55 @@ RuleBasedNumberFormat::init(const UnicodeString& rules, LocalizationInfo* locali } else { defaultRuleSet = getDefaultRuleSet(); } + originalDescription = rules; +} + +// 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 @@ -1384,7 +1675,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; } @@ -1426,6 +1717,11 @@ RuleBasedNumberFormat::dispose() ruleSets = NULL; } + if (ruleSetDescriptions) { + delete [] ruleSetDescriptions; + ruleSetDescriptions = NULL; + } + #if !UCONFIG_NO_COLLATION delete collator; #endif @@ -1434,10 +1730,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(); + } } @@ -1451,7 +1760,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 @@ -1459,7 +1768,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 @@ -1469,15 +1778,17 @@ RuleBasedNumberFormat::getCollator() const UErrorCode status = U_ZERO_ERROR; Collator* temp = Collator::createInstance(locale, status); - if (U_SUCCESS(status) && - temp->getDynamicClassID() == RuleBasedCollator::getStaticClassID()) { - - RuleBasedCollator* newCollator = (RuleBasedCollator*)temp; + RuleBasedCollator* newCollator; + if (U_SUCCESS(status) && (newCollator = dynamic_cast(temp)) != NULL) { if (lenientParseRules) { UnicodeString rules(newCollator->getRules()); rules.append(*lenientParseRules); newCollator = new RuleBasedCollator(rules, status); + // Exit if newCollator could not be created. + if (newCollator == NULL) { + return NULL; + } } else { temp = NULL; } @@ -1499,31 +1810,135 @@ 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); +} + U_NAMESPACE_END /* U_HAVE_RBNF */