michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 2008-2013, Google, International Business Machines Corporation michael@0: * and others. All Rights Reserved. michael@0: ******************************************************************************* michael@0: */ michael@0: michael@0: #include "utypeinfo.h" // for 'typeid' to work michael@0: michael@0: #include "unicode/tmutfmt.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "uvector.h" michael@0: #include "charstr.h" michael@0: #include "cmemory.h" michael@0: #include "cstring.h" michael@0: #include "hash.h" michael@0: #include "uresimp.h" michael@0: #include "unicode/msgfmt.h" michael@0: #include "uassert.h" michael@0: michael@0: #define LEFT_CURLY_BRACKET ((UChar)0x007B) michael@0: #define RIGHT_CURLY_BRACKET ((UChar)0x007D) michael@0: #define SPACE ((UChar)0x0020) michael@0: #define DIGIT_ZERO ((UChar)0x0030) michael@0: #define LOW_S ((UChar)0x0073) michael@0: #define LOW_M ((UChar)0x006D) michael@0: #define LOW_I ((UChar)0x0069) michael@0: #define LOW_N ((UChar)0x006E) michael@0: #define LOW_H ((UChar)0x0068) michael@0: #define LOW_W ((UChar)0x0077) michael@0: #define LOW_D ((UChar)0x0064) michael@0: #define LOW_Y ((UChar)0x0079) michael@0: #define LOW_Z ((UChar)0x007A) michael@0: #define LOW_E ((UChar)0x0065) michael@0: #define LOW_R ((UChar)0x0072) michael@0: #define LOW_O ((UChar)0x006F) michael@0: #define LOW_N ((UChar)0x006E) michael@0: #define LOW_T ((UChar)0x0074) michael@0: michael@0: michael@0: //TODO: define in compile time michael@0: //#define TMUTFMT_DEBUG 1 michael@0: michael@0: #ifdef TMUTFMT_DEBUG michael@0: #include michael@0: #endif michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat) michael@0: michael@0: static const char gUnitsTag[] = "units"; michael@0: static const char gShortUnitsTag[] = "unitsShort"; michael@0: static const char gTimeUnitYear[] = "year"; michael@0: static const char gTimeUnitMonth[] = "month"; michael@0: static const char gTimeUnitDay[] = "day"; michael@0: static const char gTimeUnitWeek[] = "week"; michael@0: static const char gTimeUnitHour[] = "hour"; michael@0: static const char gTimeUnitMinute[] = "minute"; michael@0: static const char gTimeUnitSecond[] = "second"; michael@0: static const char gPluralCountOther[] = "other"; michael@0: michael@0: static const UChar DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0}; michael@0: static const UChar DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0}; michael@0: michael@0: static const UChar PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0}; michael@0: static const UChar PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0}; michael@0: static const UChar PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0}; michael@0: michael@0: TimeUnitFormat::TimeUnitFormat(UErrorCode& status) michael@0: : fNumberFormat(NULL), michael@0: fPluralRules(NULL) { michael@0: create(Locale::getDefault(), UTMUTFMT_FULL_STYLE, status); michael@0: } michael@0: michael@0: michael@0: TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) michael@0: : fNumberFormat(NULL), michael@0: fPluralRules(NULL) { michael@0: create(locale, UTMUTFMT_FULL_STYLE, status); michael@0: } michael@0: michael@0: michael@0: TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) michael@0: : fNumberFormat(NULL), michael@0: fPluralRules(NULL) { michael@0: create(locale, style, status); michael@0: } michael@0: michael@0: michael@0: TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other) michael@0: : MeasureFormat(other), michael@0: fNumberFormat(NULL), michael@0: fPluralRules(NULL), michael@0: fStyle(UTMUTFMT_FULL_STYLE) michael@0: { michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: *this = other; michael@0: } michael@0: michael@0: michael@0: TimeUnitFormat::~TimeUnitFormat() { michael@0: delete fNumberFormat; michael@0: fNumberFormat = NULL; michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: deleteHash(fTimeUnitToCountToPatterns[i]); michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: delete fPluralRules; michael@0: fPluralRules = NULL; michael@0: } michael@0: michael@0: michael@0: Format* michael@0: TimeUnitFormat::clone(void) const { michael@0: return new TimeUnitFormat(*this); michael@0: } michael@0: michael@0: michael@0: TimeUnitFormat& michael@0: TimeUnitFormat::operator=(const TimeUnitFormat& other) { michael@0: if (this == &other) { michael@0: return *this; michael@0: } michael@0: delete fNumberFormat; michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: deleteHash(fTimeUnitToCountToPatterns[i]); michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: delete fPluralRules; michael@0: if (other.fNumberFormat) { michael@0: fNumberFormat = (NumberFormat*)other.fNumberFormat->clone(); michael@0: } else { michael@0: fNumberFormat = NULL; michael@0: } michael@0: fLocale = other.fLocale; michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: fTimeUnitToCountToPatterns[i] = initHash(status); michael@0: if (U_SUCCESS(status)) { michael@0: copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status); michael@0: } else { michael@0: delete fTimeUnitToCountToPatterns[i]; michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: } michael@0: if (other.fPluralRules) { michael@0: fPluralRules = (PluralRules*)other.fPluralRules->clone(); michael@0: } else { michael@0: fPluralRules = NULL; michael@0: } michael@0: fStyle = other.fStyle; michael@0: return *this; michael@0: } michael@0: michael@0: michael@0: UBool michael@0: TimeUnitFormat::operator==(const Format& other) const { michael@0: if (typeid(*this) == typeid(other)) { michael@0: TimeUnitFormat* fmt = (TimeUnitFormat*)&other; michael@0: UBool ret = ( ((fNumberFormat && fmt->fNumberFormat && *fNumberFormat == *fmt->fNumberFormat) michael@0: || fNumberFormat == fmt->fNumberFormat ) michael@0: && fLocale == fmt->fLocale michael@0: && ((fPluralRules && fmt->fPluralRules && *fPluralRules == *fmt->fPluralRules) michael@0: || fPluralRules == fmt->fPluralRules) michael@0: && fStyle == fmt->fStyle); michael@0: if (ret) { michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT && ret; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: ret = fTimeUnitToCountToPatterns[i]->equals(*(fmt->fTimeUnitToCountToPatterns[i])); michael@0: } michael@0: } michael@0: return ret; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: UnicodeString& michael@0: TimeUnitFormat::format(const Formattable& obj, UnicodeString& toAppendTo, michael@0: FieldPosition& pos, UErrorCode& status) const { michael@0: if (U_FAILURE(status)) { michael@0: return toAppendTo; michael@0: } michael@0: if (obj.getType() == Formattable::kObject) { michael@0: const UObject* formatObj = obj.getObject(); michael@0: const TimeUnitAmount* amount = dynamic_cast(formatObj); michael@0: if (amount != NULL){ michael@0: Hashtable* countToPattern = fTimeUnitToCountToPatterns[amount->getTimeUnitField()]; michael@0: double number; michael@0: const Formattable& amtNumber = amount->getNumber(); michael@0: if (amtNumber.getType() == Formattable::kDouble) { michael@0: number = amtNumber.getDouble(); michael@0: } else if (amtNumber.getType() == Formattable::kLong) { michael@0: number = amtNumber.getLong(); michael@0: } else { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return toAppendTo; michael@0: } michael@0: UnicodeString count = fPluralRules->select(number); michael@0: #ifdef TMUTFMT_DEBUG michael@0: char result[1000]; michael@0: count.extract(0, count.length(), result, "UTF-8"); michael@0: std::cout << "number: " << number << "; format plural count: " << result << "\n"; michael@0: #endif michael@0: MessageFormat* pattern = ((MessageFormat**)countToPattern->get(count))[fStyle]; michael@0: Formattable formattable[1]; michael@0: formattable[0].setDouble(number); michael@0: return pattern->format(formattable, 1, toAppendTo, pos, status); michael@0: } michael@0: } michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return toAppendTo; michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::parseObject(const UnicodeString& source, michael@0: Formattable& result, michael@0: ParsePosition& pos) const { michael@0: double resultNumber = -1; michael@0: UBool withNumberFormat = false; michael@0: TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: int32_t oldPos = pos.getIndex(); michael@0: int32_t newPos = -1; michael@0: int32_t longestParseDistance = 0; michael@0: UnicodeString* countOfLongestMatch = NULL; michael@0: #ifdef TMUTFMT_DEBUG michael@0: char res[1000]; michael@0: source.extract(0, source.length(), res, "UTF-8"); michael@0: std::cout << "parse source: " << res << "\n"; michael@0: #endif michael@0: // parse by iterating through all available patterns michael@0: // and looking for the longest match. michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i]; michael@0: int32_t elemPos = -1; michael@0: const UHashElement* elem = NULL; michael@0: while ((elem = countToPatterns->nextElement(elemPos)) != NULL){ michael@0: const UHashTok keyTok = elem->key; michael@0: UnicodeString* count = (UnicodeString*)keyTok.pointer; michael@0: #ifdef TMUTFMT_DEBUG michael@0: count->extract(0, count->length(), res, "UTF-8"); michael@0: std::cout << "parse plural count: " << res << "\n"; michael@0: #endif michael@0: const UHashTok valueTok = elem->value; michael@0: // the value is a pair of MessageFormat* michael@0: MessageFormat** patterns = (MessageFormat**)valueTok.pointer; michael@0: for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT; michael@0: style = (UTimeUnitFormatStyle)(style + 1)) { michael@0: MessageFormat* pattern = patterns[style]; michael@0: pos.setErrorIndex(-1); michael@0: pos.setIndex(oldPos); michael@0: // see if we can parse michael@0: Formattable parsed; michael@0: pattern->parseObject(source, parsed, pos); michael@0: if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) { michael@0: continue; michael@0: } michael@0: #ifdef TMUTFMT_DEBUG michael@0: std::cout << "parsed.getType: " << parsed.getType() << "\n"; michael@0: #endif michael@0: double tmpNumber = 0; michael@0: if (pattern->getArgTypeCount() != 0) { michael@0: // pattern with Number as beginning, such as "{0} d". michael@0: // check to make sure that the timeUnit is consistent michael@0: Formattable& temp = parsed[0]; michael@0: if (temp.getType() == Formattable::kDouble) { michael@0: tmpNumber = temp.getDouble(); michael@0: } else if (temp.getType() == Formattable::kLong) { michael@0: tmpNumber = temp.getLong(); michael@0: } else { michael@0: continue; michael@0: } michael@0: UnicodeString select = fPluralRules->select(tmpNumber); michael@0: #ifdef TMUTFMT_DEBUG michael@0: select.extract(0, select.length(), res, "UTF-8"); michael@0: std::cout << "parse plural select count: " << res << "\n"; michael@0: #endif michael@0: if (*count != select) { michael@0: continue; michael@0: } michael@0: } michael@0: int32_t parseDistance = pos.getIndex() - oldPos; michael@0: if (parseDistance > longestParseDistance) { michael@0: if (pattern->getArgTypeCount() != 0) { michael@0: resultNumber = tmpNumber; michael@0: withNumberFormat = true; michael@0: } else { michael@0: withNumberFormat = false; michael@0: } michael@0: resultTimeUnit = i; michael@0: newPos = pos.getIndex(); michael@0: longestParseDistance = parseDistance; michael@0: countOfLongestMatch = count; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: /* After find the longest match, parse the number. michael@0: * Result number could be null for the pattern without number pattern. michael@0: * such as unit pattern in Arabic. michael@0: * When result number is null, use plural rule to set the number. michael@0: */ michael@0: if (withNumberFormat == false && longestParseDistance != 0) { michael@0: // set the number using plurrual count michael@0: if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) { michael@0: resultNumber = 0; michael@0: } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) { michael@0: resultNumber = 1; michael@0: } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) { michael@0: resultNumber = 2; michael@0: } else { michael@0: // should not happen. michael@0: // TODO: how to handle? michael@0: resultNumber = 3; michael@0: } michael@0: } michael@0: if (longestParseDistance == 0) { michael@0: pos.setIndex(oldPos); michael@0: pos.setErrorIndex(0); michael@0: } else { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: TimeUnitAmount* tmutamt = new TimeUnitAmount(resultNumber, resultTimeUnit, status); michael@0: if (U_SUCCESS(status)) { michael@0: result.adoptObject(tmutamt); michael@0: pos.setIndex(newPos); michael@0: pos.setErrorIndex(-1); michael@0: } else { michael@0: pos.setIndex(oldPos); michael@0: pos.setErrorIndex(0); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: TimeUnitFormat::create(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return; michael@0: } michael@0: fStyle = style; michael@0: fLocale = locale; michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: //TODO: format() and parseObj() are const member functions, michael@0: //so, can not do lazy initialization in C++. michael@0: //setup has to be done in constructors. michael@0: //and here, the behavior is not consistent with Java. michael@0: //In Java, create an empty instance does not setup locale as michael@0: //default locale. If it followed by setNumberFormat(), michael@0: //in format(), the locale will set up as the locale in fNumberFormat. michael@0: //But in C++, this sets the locale as the default locale. michael@0: setup(status); michael@0: } michael@0: michael@0: void michael@0: TimeUnitFormat::setup(UErrorCode& err) { michael@0: initDataMembers(err); michael@0: michael@0: UVector pluralCounts(0, uhash_compareUnicodeString, 6, err); michael@0: StringEnumeration* keywords = fPluralRules->getKeywords(err); michael@0: if (U_FAILURE(err)) { michael@0: return; michael@0: } michael@0: UnicodeString* pluralCount; michael@0: while ((pluralCount = const_cast(keywords->snext(err))) != NULL) { michael@0: pluralCounts.addElement(pluralCount, err); michael@0: } michael@0: readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err); michael@0: checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err); michael@0: readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err); michael@0: checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err); michael@0: delete keywords; michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::initDataMembers(UErrorCode& err){ michael@0: if (U_FAILURE(err)) { michael@0: return; michael@0: } michael@0: if (fNumberFormat == NULL) { michael@0: fNumberFormat = NumberFormat::createInstance(fLocale, err); michael@0: } michael@0: delete fPluralRules; michael@0: fPluralRules = PluralRules::forLocale(fLocale, err); michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: deleteHash(fTimeUnitToCountToPatterns[i]); michael@0: fTimeUnitToCountToPatterns[i] = NULL; michael@0: } michael@0: } michael@0: michael@0: void michael@0: TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key, michael@0: const UVector& pluralCounts, UErrorCode& err) { michael@0: if (U_FAILURE(err)) { michael@0: return; michael@0: } michael@0: // fill timeUnitToCountToPatterns from resource file michael@0: // err is used to indicate wrong status except missing resource. michael@0: // status is an error code used in resource lookup. michael@0: // status does not affect "err". michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UResourceBundle *rb, *unitsRes; michael@0: rb = ures_open(NULL, fLocale.getName(), &status); michael@0: unitsRes = ures_getByKey(rb, key, NULL, &status); michael@0: unitsRes = ures_getByKey(unitsRes, "duration", unitsRes, &status); michael@0: if (U_FAILURE(status)) { michael@0: ures_close(unitsRes); michael@0: ures_close(rb); michael@0: return; michael@0: } michael@0: int32_t size = ures_getSize(unitsRes); michael@0: for ( int32_t index = 0; index < size; ++index) { michael@0: // resource of one time unit michael@0: UResourceBundle* oneTimeUnit = ures_getByIndex(unitsRes, index, michael@0: NULL, &status); michael@0: if (U_SUCCESS(status)) { michael@0: const char* timeUnitName = ures_getKey(oneTimeUnit); michael@0: if (timeUnitName == NULL) { michael@0: ures_close(oneTimeUnit); michael@0: continue; michael@0: } michael@0: UResourceBundle* countsToPatternRB = ures_getByKey(unitsRes, michael@0: timeUnitName, michael@0: NULL, &status); michael@0: if (countsToPatternRB == NULL || U_FAILURE(status)) { michael@0: ures_close(countsToPatternRB); michael@0: ures_close(oneTimeUnit); michael@0: continue; michael@0: } michael@0: TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_YEAR; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_MONTH; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_DAY; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_HOUR; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_MINUTE; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_SECOND; michael@0: } else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) { michael@0: timeUnitField = TimeUnit::UTIMEUNIT_WEEK; michael@0: } else { michael@0: ures_close(countsToPatternRB); michael@0: ures_close(oneTimeUnit); michael@0: continue; michael@0: } michael@0: Hashtable* countToPatterns = fTimeUnitToCountToPatterns[timeUnitField]; michael@0: if (countToPatterns == NULL) { michael@0: countToPatterns = initHash(err); michael@0: if (U_FAILURE(err)) { michael@0: ures_close(countsToPatternRB); michael@0: ures_close(oneTimeUnit); michael@0: delete countToPatterns; michael@0: break; michael@0: } michael@0: } michael@0: int32_t count = ures_getSize(countsToPatternRB); michael@0: const char* pluralCount; michael@0: for ( int32_t pluralIndex = 0; pluralIndex < count; ++pluralIndex) { michael@0: // resource of count to pattern michael@0: UnicodeString pattern = michael@0: ures_getNextUnicodeString(countsToPatternRB, &pluralCount, &status); michael@0: if (U_FAILURE(status)) { michael@0: continue; michael@0: } michael@0: UnicodeString pluralCountUniStr(pluralCount, -1, US_INV); michael@0: if (!pluralCounts.contains(&pluralCountUniStr)) { michael@0: continue; michael@0: } michael@0: MessageFormat* messageFormat = new MessageFormat(pattern, fLocale, err); michael@0: if ( U_SUCCESS(err) ) { michael@0: if (fNumberFormat != NULL) { michael@0: messageFormat->setFormat(0, *fNumberFormat); michael@0: } michael@0: MessageFormat** formatters = (MessageFormat**)countToPatterns->get(pluralCountUniStr); michael@0: if (formatters == NULL) { michael@0: formatters = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)); michael@0: formatters[UTMUTFMT_FULL_STYLE] = NULL; michael@0: formatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; michael@0: countToPatterns->put(pluralCountUniStr, formatters, err); michael@0: if (U_FAILURE(err)) { michael@0: uprv_free(formatters); michael@0: } michael@0: } michael@0: if (U_SUCCESS(err)) { michael@0: //delete formatters[style]; michael@0: formatters[style] = messageFormat; michael@0: } michael@0: } michael@0: if (U_FAILURE(err)) { michael@0: ures_close(countsToPatternRB); michael@0: ures_close(oneTimeUnit); michael@0: ures_close(unitsRes); michael@0: ures_close(rb); michael@0: delete messageFormat; michael@0: delete countToPatterns; michael@0: return; michael@0: } michael@0: } michael@0: if (fTimeUnitToCountToPatterns[timeUnitField] == NULL) { michael@0: fTimeUnitToCountToPatterns[timeUnitField] = countToPatterns; michael@0: } michael@0: ures_close(countsToPatternRB); michael@0: } michael@0: ures_close(oneTimeUnit); michael@0: } michael@0: ures_close(unitsRes); michael@0: ures_close(rb); michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) { michael@0: if (U_FAILURE(err)) { michael@0: return; michael@0: } michael@0: // there should be patterns for each plural rule in each time unit. michael@0: // For each time unit, michael@0: // for each plural rule, following is unit pattern fall-back rule: michael@0: // ( for example: "one" hour ) michael@0: // look for its unit pattern in its locale tree. michael@0: // if pattern is not found in its own locale, such as de_DE, michael@0: // look for the pattern in its parent, such as de, michael@0: // keep looking till found or till root. michael@0: // if the pattern is not found in root either, michael@0: // fallback to plural count "other", michael@0: // look for the pattern of "other" in the locale tree: michael@0: // "de_DE" to "de" to "root". michael@0: // If not found, fall back to value of michael@0: // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h". michael@0: // michael@0: // Following is consistency check to create pattern for each michael@0: // plural rule in each time unit using above fall-back rule. michael@0: // michael@0: StringEnumeration* keywords = fPluralRules->getKeywords(err); michael@0: if (U_SUCCESS(err)) { michael@0: const UnicodeString* pluralCount; michael@0: while ((pluralCount = keywords->snext(err)) != NULL) { michael@0: if ( U_SUCCESS(err) ) { michael@0: for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) { michael@0: // for each time unit, michael@0: // get all the patterns for each plural rule in this locale. michael@0: Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i]; michael@0: if ( countToPatterns == NULL ) { michael@0: countToPatterns = initHash(err); michael@0: if (U_FAILURE(err)) { michael@0: delete countToPatterns; michael@0: return; michael@0: } michael@0: fTimeUnitToCountToPatterns[i] = countToPatterns; michael@0: } michael@0: MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount); michael@0: if( formatters == NULL || formatters[style] == NULL ) { michael@0: // look through parents michael@0: const char* localeName = fLocale.getName(); michael@0: CharString pluralCountChars; michael@0: pluralCountChars.appendInvariantChars(*pluralCount, err); michael@0: searchInLocaleChain(style, key, localeName, michael@0: (TimeUnit::UTimeUnitFields)i, michael@0: *pluralCount, pluralCountChars.data(), michael@0: countToPatterns, err); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: delete keywords; michael@0: } michael@0: michael@0: michael@0: michael@0: // srcPluralCount is the original plural count on which the pattern is michael@0: // searched for. michael@0: // searchPluralCount is the fallback plural count. michael@0: // For example, to search for pattern for ""one" hour", michael@0: // "one" is the srcPluralCount, michael@0: // if the pattern is not found even in root, fallback to michael@0: // using patterns of plural count "other", michael@0: // then, "other" is the searchPluralCount. michael@0: void michael@0: TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName, michael@0: TimeUnit::UTimeUnitFields srcTimeUnitField, michael@0: const UnicodeString& srcPluralCount, michael@0: const char* searchPluralCount, michael@0: Hashtable* countToPatterns, michael@0: UErrorCode& err) { michael@0: if (U_FAILURE(err)) { michael@0: return; michael@0: } michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: char parentLocale[ULOC_FULLNAME_CAPACITY]; michael@0: uprv_strcpy(parentLocale, localeName); michael@0: int32_t locNameLen; michael@0: U_ASSERT(countToPatterns != NULL); michael@0: while ((locNameLen = uloc_getParent(parentLocale, parentLocale, michael@0: ULOC_FULLNAME_CAPACITY, &status)) >= 0){ michael@0: // look for pattern for srcPluralCount in locale tree michael@0: UResourceBundle *rb, *unitsRes, *countsToPatternRB; michael@0: rb = ures_open(NULL, parentLocale, &status); michael@0: unitsRes = ures_getByKey(rb, key, NULL, &status); michael@0: const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status); michael@0: countsToPatternRB = ures_getByKey(unitsRes, timeUnitName, NULL, &status); michael@0: const UChar* pattern; michael@0: int32_t ptLength; michael@0: pattern = ures_getStringByKeyWithFallback(countsToPatternRB, searchPluralCount, &ptLength, &status); michael@0: if (U_SUCCESS(status)) { michael@0: //found michael@0: MessageFormat* messageFormat = new MessageFormat(UnicodeString(TRUE, pattern, ptLength), fLocale, err); michael@0: if (U_SUCCESS(err)) { michael@0: if (fNumberFormat != NULL) { michael@0: messageFormat->setFormat(0, *fNumberFormat); michael@0: } michael@0: MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); michael@0: if (formatters == NULL) { michael@0: formatters = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)); michael@0: formatters[UTMUTFMT_FULL_STYLE] = NULL; michael@0: formatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; michael@0: countToPatterns->put(srcPluralCount, formatters, err); michael@0: if (U_FAILURE(err)) { michael@0: uprv_free(formatters); michael@0: delete messageFormat; michael@0: } michael@0: } michael@0: if (U_SUCCESS(err)) { michael@0: //delete formatters[style]; michael@0: formatters[style] = messageFormat; michael@0: } michael@0: } else { michael@0: delete messageFormat; michael@0: } michael@0: ures_close(countsToPatternRB); michael@0: ures_close(unitsRes); michael@0: ures_close(rb); michael@0: return; michael@0: } michael@0: ures_close(countsToPatternRB); michael@0: ures_close(unitsRes); michael@0: ures_close(rb); michael@0: status = U_ZERO_ERROR; michael@0: if ( locNameLen ==0 ) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // if no unitsShort resource was found even after fallback to root locale michael@0: // then search the units resource fallback from the current level to root michael@0: if ( locNameLen == 0 && uprv_strcmp(key, gShortUnitsTag) == 0) { michael@0: #ifdef TMUTFMT_DEBUG michael@0: std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n"; michael@0: #endif michael@0: char pLocale[ULOC_FULLNAME_CAPACITY]; michael@0: uprv_strcpy(pLocale, localeName); michael@0: // Add an underscore at the tail of locale name, michael@0: // so that searchInLocaleChain will check the current locale before falling back michael@0: uprv_strcat(pLocale, "_"); michael@0: searchInLocaleChain(style, gUnitsTag, pLocale, srcTimeUnitField, srcPluralCount, michael@0: searchPluralCount, countToPatterns, err); michael@0: MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); michael@0: if (formatters != NULL && formatters[style] != NULL) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // if not found the pattern for this plural count at all, michael@0: // fall-back to plural count "other" michael@0: if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) { michael@0: // set default fall back the same as the resource in root michael@0: MessageFormat* messageFormat = NULL; michael@0: const UChar *pattern = NULL; michael@0: if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) { michael@0: pattern = DEFAULT_PATTERN_FOR_SECOND; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) { michael@0: pattern = DEFAULT_PATTERN_FOR_MINUTE; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) { michael@0: pattern = DEFAULT_PATTERN_FOR_HOUR; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) { michael@0: pattern = DEFAULT_PATTERN_FOR_WEEK; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) { michael@0: pattern = DEFAULT_PATTERN_FOR_DAY; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) { michael@0: pattern = DEFAULT_PATTERN_FOR_MONTH; michael@0: } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) { michael@0: pattern = DEFAULT_PATTERN_FOR_YEAR; michael@0: } michael@0: if (pattern != NULL) { michael@0: messageFormat = new MessageFormat(UnicodeString(TRUE, pattern, -1), fLocale, err); michael@0: } michael@0: if (U_SUCCESS(err)) { michael@0: if (fNumberFormat != NULL && messageFormat != NULL) { michael@0: messageFormat->setFormat(0, *fNumberFormat); michael@0: } michael@0: MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); michael@0: if (formatters == NULL) { michael@0: formatters = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)); michael@0: formatters[UTMUTFMT_FULL_STYLE] = NULL; michael@0: formatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; michael@0: countToPatterns->put(srcPluralCount, formatters, err); michael@0: if (U_FAILURE(err)) { michael@0: uprv_free(formatters); michael@0: delete messageFormat; michael@0: } michael@0: } michael@0: if (U_SUCCESS(err)) { michael@0: //delete formatters[style]; michael@0: formatters[style] = messageFormat; michael@0: } michael@0: } else { michael@0: delete messageFormat; michael@0: } michael@0: } else { michael@0: // fall back to rule "other", and search in parents michael@0: searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount, michael@0: gPluralCountOther, countToPatterns, err); michael@0: } michael@0: } michael@0: michael@0: void michael@0: TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) { michael@0: if (U_SUCCESS(status) && fLocale != locale) { michael@0: fLocale = locale; michael@0: setup(status); michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){ michael@0: if (U_FAILURE(status) || (fNumberFormat && format == *fNumberFormat)) { michael@0: return; michael@0: } michael@0: delete fNumberFormat; michael@0: fNumberFormat = (NumberFormat*)format.clone(); michael@0: // reset the number formatter in the fTimeUnitToCountToPatterns map michael@0: for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; michael@0: i < TimeUnit::UTIMEUNIT_FIELD_COUNT; michael@0: i = (TimeUnit::UTimeUnitFields)(i+1)) { michael@0: int32_t pos = -1; michael@0: const UHashElement* elem = NULL; michael@0: while ((elem = fTimeUnitToCountToPatterns[i]->nextElement(pos)) != NULL){ michael@0: const UHashTok keyTok = elem->value; michael@0: MessageFormat** pattern = (MessageFormat**)keyTok.pointer; michael@0: michael@0: pattern[UTMUTFMT_FULL_STYLE]->setFormat(0, format); michael@0: pattern[UTMUTFMT_ABBREVIATED_STYLE]->setFormat(0, format); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::deleteHash(Hashtable* htable) { michael@0: int32_t pos = -1; michael@0: const UHashElement* element = NULL; michael@0: if ( htable ) { michael@0: while ( (element = htable->nextElement(pos)) != NULL ) { michael@0: const UHashTok valueTok = element->value; michael@0: const MessageFormat** value = (const MessageFormat**)valueTok.pointer; michael@0: delete value[UTMUTFMT_FULL_STYLE]; michael@0: delete value[UTMUTFMT_ABBREVIATED_STYLE]; michael@0: //delete[] value; michael@0: uprv_free(value); michael@0: } michael@0: } michael@0: delete htable; michael@0: } michael@0: michael@0: michael@0: void michael@0: TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) { michael@0: if ( U_FAILURE(status) ) { michael@0: return; michael@0: } michael@0: int32_t pos = -1; michael@0: const UHashElement* element = NULL; michael@0: if ( source ) { michael@0: while ( (element = source->nextElement(pos)) != NULL ) { michael@0: const UHashTok keyTok = element->key; michael@0: const UnicodeString* key = (UnicodeString*)keyTok.pointer; michael@0: const UHashTok valueTok = element->value; michael@0: const MessageFormat** value = (const MessageFormat**)valueTok.pointer; michael@0: MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)); michael@0: newVal[0] = (MessageFormat*)value[0]->clone(); michael@0: newVal[1] = (MessageFormat*)value[1]->clone(); michael@0: target->put(UnicodeString(*key), newVal, status); michael@0: if ( U_FAILURE(status) ) { michael@0: delete newVal[0]; michael@0: delete newVal[1]; michael@0: uprv_free(newVal); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: U_CDECL_BEGIN michael@0: michael@0: /** michael@0: * set hash table value comparator michael@0: * michael@0: * @param val1 one value in comparison michael@0: * @param val2 the other value in comparison michael@0: * @return TRUE if 2 values are the same, FALSE otherwise michael@0: */ michael@0: static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2); michael@0: michael@0: static UBool michael@0: U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) { michael@0: const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer; michael@0: const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer; michael@0: return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1]; michael@0: } michael@0: michael@0: U_CDECL_END michael@0: michael@0: Hashtable* michael@0: TimeUnitFormat::initHash(UErrorCode& status) { michael@0: if ( U_FAILURE(status) ) { michael@0: return NULL; michael@0: } michael@0: Hashtable* hTable; michael@0: if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return NULL; michael@0: } michael@0: if ( U_FAILURE(status) ) { michael@0: delete hTable; michael@0: return NULL; michael@0: } michael@0: hTable->setValueComparator(tmutfmtHashTableValueComparator); michael@0: return hTable; michael@0: } michael@0: michael@0: michael@0: const char* michael@0: TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField, michael@0: UErrorCode& status) { michael@0: if (U_FAILURE(status)) { michael@0: return NULL; michael@0: } michael@0: switch (unitField) { michael@0: case TimeUnit::UTIMEUNIT_YEAR: michael@0: return gTimeUnitYear; michael@0: case TimeUnit::UTIMEUNIT_MONTH: michael@0: return gTimeUnitMonth; michael@0: case TimeUnit::UTIMEUNIT_DAY: michael@0: return gTimeUnitDay; michael@0: case TimeUnit::UTIMEUNIT_WEEK: michael@0: return gTimeUnitWeek; michael@0: case TimeUnit::UTIMEUNIT_HOUR: michael@0: return gTimeUnitHour; michael@0: case TimeUnit::UTIMEUNIT_MINUTE: michael@0: return gTimeUnitMinute; michael@0: case TimeUnit::UTIMEUNIT_SECOND: michael@0: return gTimeUnitSecond; michael@0: default: michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return NULL; michael@0: } michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif