mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
d9409e29f8
Depends on D143273 Differential Revision: https://phabricator.services.mozilla.com/D143275
807 lines
31 KiB
C++
807 lines
31 KiB
C++
// © 2016 and later: Unicode, Inc. and others.
|
|
// License & terms of use: http://www.unicode.org/copyright.html
|
|
/*
|
|
*******************************************************************************
|
|
* Copyright (C) 2008-2015, Google, International Business Machines Corporation
|
|
* and others. All Rights Reserved.
|
|
*******************************************************************************
|
|
*/
|
|
|
|
#include "unicode/tmutfmt.h"
|
|
|
|
#if !UCONFIG_NO_FORMATTING
|
|
|
|
#include "unicode/decimfmt.h"
|
|
#include "unicode/localpointer.h"
|
|
#include "plurrule_impl.h"
|
|
#include "uvector.h"
|
|
#include "charstr.h"
|
|
#include "cmemory.h"
|
|
#include "cstring.h"
|
|
#include "hash.h"
|
|
#include "uresimp.h"
|
|
#include "ureslocs.h"
|
|
#include "unicode/msgfmt.h"
|
|
#include "uassert.h"
|
|
|
|
#define LEFT_CURLY_BRACKET ((UChar)0x007B)
|
|
#define RIGHT_CURLY_BRACKET ((UChar)0x007D)
|
|
#define SPACE ((UChar)0x0020)
|
|
#define DIGIT_ZERO ((UChar)0x0030)
|
|
#define LOW_S ((UChar)0x0073)
|
|
#define LOW_M ((UChar)0x006D)
|
|
#define LOW_I ((UChar)0x0069)
|
|
#define LOW_N ((UChar)0x006E)
|
|
#define LOW_H ((UChar)0x0068)
|
|
#define LOW_W ((UChar)0x0077)
|
|
#define LOW_D ((UChar)0x0064)
|
|
#define LOW_Y ((UChar)0x0079)
|
|
#define LOW_Z ((UChar)0x007A)
|
|
#define LOW_E ((UChar)0x0065)
|
|
#define LOW_R ((UChar)0x0072)
|
|
#define LOW_O ((UChar)0x006F)
|
|
#define LOW_N ((UChar)0x006E)
|
|
#define LOW_T ((UChar)0x0074)
|
|
|
|
|
|
//TODO: define in compile time
|
|
//#define TMUTFMT_DEBUG 1
|
|
|
|
#ifdef TMUTFMT_DEBUG
|
|
#include <iostream>
|
|
#endif
|
|
|
|
U_NAMESPACE_BEGIN
|
|
|
|
|
|
|
|
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat)
|
|
|
|
static const char gUnitsTag[] = "units";
|
|
static const char gShortUnitsTag[] = "unitsShort";
|
|
static const char gTimeUnitYear[] = "year";
|
|
static const char gTimeUnitMonth[] = "month";
|
|
static const char gTimeUnitDay[] = "day";
|
|
static const char gTimeUnitWeek[] = "week";
|
|
static const char gTimeUnitHour[] = "hour";
|
|
static const char gTimeUnitMinute[] = "minute";
|
|
static const char gTimeUnitSecond[] = "second";
|
|
static const char gPluralCountOther[] = "other";
|
|
|
|
static const UChar DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0};
|
|
static const UChar DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0};
|
|
|
|
static const UChar PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0};
|
|
static const UChar PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0};
|
|
static const UChar PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0};
|
|
|
|
TimeUnitFormat::TimeUnitFormat(UErrorCode& status) {
|
|
initMeasureFormat(Locale::getDefault(), UMEASFMT_WIDTH_WIDE, NULL, status);
|
|
create(UTMUTFMT_FULL_STYLE, status);
|
|
}
|
|
|
|
|
|
TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) {
|
|
initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status);
|
|
create(UTMUTFMT_FULL_STYLE, status);
|
|
}
|
|
|
|
|
|
TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) {
|
|
switch (style) {
|
|
case UTMUTFMT_FULL_STYLE:
|
|
initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status);
|
|
break;
|
|
case UTMUTFMT_ABBREVIATED_STYLE:
|
|
initMeasureFormat(locale, UMEASFMT_WIDTH_SHORT, NULL, status);
|
|
break;
|
|
default:
|
|
initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status);
|
|
break;
|
|
}
|
|
create(style, status);
|
|
}
|
|
|
|
TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other)
|
|
: MeasureFormat(other),
|
|
fStyle(other.fStyle)
|
|
{
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
fTimeUnitToCountToPatterns[i] = initHash(status);
|
|
if (U_SUCCESS(status)) {
|
|
copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
|
|
} else {
|
|
delete fTimeUnitToCountToPatterns[i];
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
TimeUnitFormat::~TimeUnitFormat() {
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
deleteHash(fTimeUnitToCountToPatterns[i]);
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
TimeUnitFormat*
|
|
TimeUnitFormat::clone() const {
|
|
return new TimeUnitFormat(*this);
|
|
}
|
|
|
|
|
|
TimeUnitFormat&
|
|
TimeUnitFormat::operator=(const TimeUnitFormat& other) {
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
MeasureFormat::operator=(other);
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
deleteHash(fTimeUnitToCountToPatterns[i]);
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
fTimeUnitToCountToPatterns[i] = initHash(status);
|
|
if (U_SUCCESS(status)) {
|
|
copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status);
|
|
} else {
|
|
delete fTimeUnitToCountToPatterns[i];
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
}
|
|
fStyle = other.fStyle;
|
|
return *this;
|
|
}
|
|
|
|
void
|
|
TimeUnitFormat::parseObject(const UnicodeString& source,
|
|
Formattable& result,
|
|
ParsePosition& pos) const {
|
|
Formattable resultNumber(0.0);
|
|
UBool withNumberFormat = false;
|
|
TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
int32_t oldPos = pos.getIndex();
|
|
int32_t newPos = -1;
|
|
int32_t longestParseDistance = 0;
|
|
UnicodeString* countOfLongestMatch = NULL;
|
|
#ifdef TMUTFMT_DEBUG
|
|
char res[1000];
|
|
source.extract(0, source.length(), res, "UTF-8");
|
|
std::cout << "parse source: " << res << "\n";
|
|
#endif
|
|
// parse by iterating through all available patterns
|
|
// and looking for the longest match.
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
|
|
int32_t elemPos = UHASH_FIRST;
|
|
const UHashElement* elem = NULL;
|
|
while ((elem = countToPatterns->nextElement(elemPos)) != NULL){
|
|
const UHashTok keyTok = elem->key;
|
|
UnicodeString* count = (UnicodeString*)keyTok.pointer;
|
|
#ifdef TMUTFMT_DEBUG
|
|
count->extract(0, count->length(), res, "UTF-8");
|
|
std::cout << "parse plural count: " << res << "\n";
|
|
#endif
|
|
const UHashTok valueTok = elem->value;
|
|
// the value is a pair of MessageFormat*
|
|
MessageFormat** patterns = (MessageFormat**)valueTok.pointer;
|
|
for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT;
|
|
style = (UTimeUnitFormatStyle)(style + 1)) {
|
|
MessageFormat* pattern = patterns[style];
|
|
pos.setErrorIndex(-1);
|
|
pos.setIndex(oldPos);
|
|
// see if we can parse
|
|
Formattable parsed;
|
|
pattern->parseObject(source, parsed, pos);
|
|
if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) {
|
|
continue;
|
|
}
|
|
#ifdef TMUTFMT_DEBUG
|
|
std::cout << "parsed.getType: " << parsed.getType() << "\n";
|
|
#endif
|
|
Formattable tmpNumber(0.0);
|
|
if (pattern->getArgTypeCount() != 0) {
|
|
Formattable& temp = parsed[0];
|
|
if (temp.getType() == Formattable::kString) {
|
|
UnicodeString tmpString;
|
|
UErrorCode pStatus = U_ZERO_ERROR;
|
|
getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus);
|
|
if (U_FAILURE(pStatus)) {
|
|
continue;
|
|
}
|
|
} else if (temp.isNumeric()) {
|
|
tmpNumber = temp;
|
|
} else {
|
|
continue;
|
|
}
|
|
}
|
|
int32_t parseDistance = pos.getIndex() - oldPos;
|
|
if (parseDistance > longestParseDistance) {
|
|
if (pattern->getArgTypeCount() != 0) {
|
|
resultNumber = tmpNumber;
|
|
withNumberFormat = true;
|
|
} else {
|
|
withNumberFormat = false;
|
|
}
|
|
resultTimeUnit = i;
|
|
newPos = pos.getIndex();
|
|
longestParseDistance = parseDistance;
|
|
countOfLongestMatch = count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* After find the longest match, parse the number.
|
|
* Result number could be null for the pattern without number pattern.
|
|
* such as unit pattern in Arabic.
|
|
* When result number is null, use plural rule to set the number.
|
|
*/
|
|
if (withNumberFormat == false && longestParseDistance != 0) {
|
|
// set the number using plurrual count
|
|
if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) {
|
|
resultNumber = Formattable(0.0);
|
|
} else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) {
|
|
resultNumber = Formattable(1.0);
|
|
} else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) {
|
|
resultNumber = Formattable(2.0);
|
|
} else {
|
|
// should not happen.
|
|
// TODO: how to handle?
|
|
resultNumber = Formattable(3.0);
|
|
}
|
|
}
|
|
if (longestParseDistance == 0) {
|
|
pos.setIndex(oldPos);
|
|
pos.setErrorIndex(0);
|
|
} else {
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
LocalPointer<TimeUnitAmount> tmutamt(new TimeUnitAmount(resultNumber, resultTimeUnit, status), status);
|
|
if (U_SUCCESS(status)) {
|
|
result.adoptObject(tmutamt.orphan());
|
|
pos.setIndex(newPos);
|
|
pos.setErrorIndex(-1);
|
|
} else {
|
|
pos.setIndex(oldPos);
|
|
pos.setErrorIndex(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
TimeUnitFormat::create(UTimeUnitFormatStyle style, UErrorCode& status) {
|
|
// fTimeUnitToCountToPatterns[] must have its elements initialized to NULL first
|
|
// before checking for failure status.
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) {
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return;
|
|
}
|
|
fStyle = style;
|
|
|
|
//TODO: format() and parseObj() are const member functions,
|
|
//so, can not do lazy initialization in C++.
|
|
//setup has to be done in constructors.
|
|
//and here, the behavior is not consistent with Java.
|
|
//In Java, create an empty instance does not setup locale as
|
|
//default locale. If it followed by setNumberFormat(),
|
|
//in format(), the locale will set up as the locale in fNumberFormat.
|
|
//But in C++, this sets the locale as the default locale.
|
|
setup(status);
|
|
}
|
|
|
|
void
|
|
TimeUnitFormat::setup(UErrorCode& err) {
|
|
initDataMembers(err);
|
|
|
|
UVector pluralCounts(nullptr, uhash_compareUnicodeString, 6, err);
|
|
LocalPointer<StringEnumeration> keywords(getPluralRules().getKeywords(err), err);
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
UnicodeString* pluralCount;
|
|
while ((pluralCount = const_cast<UnicodeString*>(keywords->snext(err))) != NULL) {
|
|
pluralCounts.addElement(pluralCount, err);
|
|
}
|
|
readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err);
|
|
checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err);
|
|
readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err);
|
|
checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err);
|
|
}
|
|
|
|
|
|
void
|
|
TimeUnitFormat::initDataMembers(UErrorCode& err){
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR;
|
|
i < TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
i = (TimeUnit::UTimeUnitFields)(i+1)) {
|
|
deleteHash(fTimeUnitToCountToPatterns[i]);
|
|
fTimeUnitToCountToPatterns[i] = NULL;
|
|
}
|
|
}
|
|
|
|
struct TimeUnitFormatReadSink : public ResourceSink {
|
|
TimeUnitFormat *timeUnitFormatObj;
|
|
const UVector &pluralCounts;
|
|
UTimeUnitFormatStyle style;
|
|
UBool beenHere;
|
|
|
|
TimeUnitFormatReadSink(TimeUnitFormat *timeUnitFormatObj,
|
|
const UVector &pluralCounts, UTimeUnitFormatStyle style) :
|
|
timeUnitFormatObj(timeUnitFormatObj), pluralCounts(pluralCounts),
|
|
style(style), beenHere(FALSE){}
|
|
|
|
virtual ~TimeUnitFormatReadSink();
|
|
|
|
virtual void put(const char *key, ResourceValue &value, UBool, UErrorCode &errorCode) override {
|
|
// Skip all put() calls except the first one -- discard all fallback data.
|
|
if (beenHere) {
|
|
return;
|
|
} else {
|
|
beenHere = TRUE;
|
|
}
|
|
|
|
ResourceTable units = value.getTable(errorCode);
|
|
if (U_FAILURE(errorCode)) { return; }
|
|
|
|
for (int32_t i = 0; units.getKeyAndValue(i, key, value); ++i) {
|
|
const char* timeUnitName = key;
|
|
if (timeUnitName == NULL) {
|
|
continue;
|
|
}
|
|
|
|
TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT;
|
|
if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_YEAR;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_MONTH;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_DAY;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_HOUR;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_MINUTE;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_SECOND;
|
|
} else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) {
|
|
timeUnitField = TimeUnit::UTIMEUNIT_WEEK;
|
|
} else {
|
|
continue;
|
|
}
|
|
LocalPointer<Hashtable> localCountToPatterns;
|
|
Hashtable *countToPatterns =
|
|
timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField];
|
|
if (countToPatterns == NULL) {
|
|
localCountToPatterns.adoptInsteadAndCheckErrorCode(
|
|
timeUnitFormatObj->initHash(errorCode), errorCode);
|
|
countToPatterns = localCountToPatterns.getAlias();
|
|
if (U_FAILURE(errorCode)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ResourceTable countsToPatternTable = value.getTable(errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
continue;
|
|
}
|
|
for (int32_t j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) {
|
|
errorCode = U_ZERO_ERROR;
|
|
UnicodeString pattern = value.getUnicodeString(errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
continue;
|
|
}
|
|
UnicodeString pluralCountUniStr(key, -1, US_INV);
|
|
if (!pluralCounts.contains(&pluralCountUniStr)) {
|
|
continue;
|
|
}
|
|
LocalPointer<MessageFormat> messageFormat(new MessageFormat(
|
|
pattern, timeUnitFormatObj->getLocale(errorCode), errorCode), errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
return;
|
|
}
|
|
MessageFormat** formatters =
|
|
(MessageFormat**)countToPatterns->get(pluralCountUniStr);
|
|
if (formatters == NULL) {
|
|
LocalMemory<MessageFormat *> localFormatters(
|
|
(MessageFormat **)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
|
|
if (localFormatters.isNull()) {
|
|
errorCode = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
localFormatters[UTMUTFMT_FULL_STYLE] = NULL;
|
|
localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL;
|
|
countToPatterns->put(pluralCountUniStr, localFormatters.getAlias(), errorCode);
|
|
if (U_FAILURE(errorCode)) {
|
|
return;
|
|
}
|
|
formatters = localFormatters.orphan();
|
|
}
|
|
formatters[style] = messageFormat.orphan();
|
|
}
|
|
|
|
if (timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] == NULL) {
|
|
timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] = localCountToPatterns.orphan();
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
TimeUnitFormatReadSink::~TimeUnitFormatReadSink() {}
|
|
|
|
void
|
|
TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key,
|
|
const UVector& pluralCounts, UErrorCode& err) {
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
// fill timeUnitToCountToPatterns from resource file
|
|
// err is used to indicate wrong status except missing resource.
|
|
// status is an error code used in resource lookup.
|
|
// status does not affect "err".
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, getLocaleID(status), &status));
|
|
|
|
LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, NULL, &status));
|
|
ures_getByKey(unitsRes.getAlias(), "duration", unitsRes.getAlias(), &status);
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
|
|
TimeUnitFormatReadSink sink(this, pluralCounts, style);
|
|
ures_getAllItemsWithFallback(unitsRes.getAlias(), "", sink, status);
|
|
}
|
|
|
|
void
|
|
TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) {
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
// there should be patterns for each plural rule in each time unit.
|
|
// For each time unit,
|
|
// for each plural rule, following is unit pattern fall-back rule:
|
|
// ( for example: "one" hour )
|
|
// look for its unit pattern in its locale tree.
|
|
// if pattern is not found in its own locale, such as de_DE,
|
|
// look for the pattern in its parent, such as de,
|
|
// keep looking till found or till root.
|
|
// if the pattern is not found in root either,
|
|
// fallback to plural count "other",
|
|
// look for the pattern of "other" in the locale tree:
|
|
// "de_DE" to "de" to "root".
|
|
// If not found, fall back to value of
|
|
// static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h".
|
|
//
|
|
// Following is consistency check to create pattern for each
|
|
// plural rule in each time unit using above fall-back rule.
|
|
//
|
|
LocalPointer<StringEnumeration> keywords(
|
|
getPluralRules().getKeywords(err), err);
|
|
const UnicodeString* pluralCount;
|
|
while (U_SUCCESS(err) && (pluralCount = keywords->snext(err)) != NULL) {
|
|
for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) {
|
|
// for each time unit,
|
|
// get all the patterns for each plural rule in this locale.
|
|
Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i];
|
|
if ( countToPatterns == NULL ) {
|
|
fTimeUnitToCountToPatterns[i] = countToPatterns = initHash(err);
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
}
|
|
MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount);
|
|
if( formatters == NULL || formatters[style] == NULL ) {
|
|
// look through parents
|
|
const char* localeName = getLocaleID(err);
|
|
CharString pluralCountChars;
|
|
pluralCountChars.appendInvariantChars(*pluralCount, err);
|
|
searchInLocaleChain(style, key, localeName,
|
|
(TimeUnit::UTimeUnitFields)i,
|
|
*pluralCount, pluralCountChars.data(),
|
|
countToPatterns, err);
|
|
}
|
|
// TODO: what to do with U_FAILURE(err) at this point.
|
|
// As is, the outer loop continues to run, but does nothing.
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// srcPluralCount is the original plural count on which the pattern is
|
|
// searched for.
|
|
// searchPluralCount is the fallback plural count.
|
|
// For example, to search for pattern for ""one" hour",
|
|
// "one" is the srcPluralCount,
|
|
// if the pattern is not found even in root, fallback to
|
|
// using patterns of plural count "other",
|
|
// then, "other" is the searchPluralCount.
|
|
void
|
|
TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName,
|
|
TimeUnit::UTimeUnitFields srcTimeUnitField,
|
|
const UnicodeString& srcPluralCount,
|
|
const char* searchPluralCount,
|
|
Hashtable* countToPatterns,
|
|
UErrorCode& err) {
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
char parentLocale[ULOC_FULLNAME_CAPACITY];
|
|
uprv_strcpy(parentLocale, localeName);
|
|
int32_t locNameLen;
|
|
U_ASSERT(countToPatterns != NULL);
|
|
while ((locNameLen = uloc_getParent(parentLocale, parentLocale,
|
|
ULOC_FULLNAME_CAPACITY, &status)) >= 0){
|
|
// look for pattern for srcPluralCount in locale tree
|
|
LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, parentLocale, &status));
|
|
LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, NULL, &status));
|
|
const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status);
|
|
LocalUResourceBundlePointer countsToPatternRB(ures_getByKey(unitsRes.getAlias(), timeUnitName, NULL, &status));
|
|
const UChar* pattern;
|
|
int32_t ptLength;
|
|
pattern = ures_getStringByKeyWithFallback(countsToPatternRB.getAlias(), searchPluralCount, &ptLength, &status);
|
|
if (U_SUCCESS(status)) {
|
|
//found
|
|
LocalPointer<MessageFormat> messageFormat(
|
|
new MessageFormat(UnicodeString(TRUE, pattern, ptLength), getLocale(err), err), err);
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
|
|
if (formatters == NULL) {
|
|
LocalMemory<MessageFormat *> localFormatters(
|
|
(MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
|
|
formatters = localFormatters.getAlias();
|
|
localFormatters[UTMUTFMT_FULL_STYLE] = NULL;
|
|
localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL;
|
|
countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
}
|
|
//delete formatters[style];
|
|
formatters[style] = messageFormat.orphan();
|
|
return;
|
|
}
|
|
status = U_ZERO_ERROR;
|
|
if (locNameLen == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if no unitsShort resource was found even after fallback to root locale
|
|
// then search the units resource fallback from the current level to root
|
|
if ( locNameLen == 0 && uprv_strcmp(key, gShortUnitsTag) == 0) {
|
|
#ifdef TMUTFMT_DEBUG
|
|
std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n";
|
|
#endif
|
|
CharString pLocale(localeName, -1, err);
|
|
// Add an underscore at the tail of locale name,
|
|
// so that searchInLocaleChain will check the current locale before falling back
|
|
pLocale.append('_', err);
|
|
searchInLocaleChain(style, gUnitsTag, pLocale.data(), srcTimeUnitField, srcPluralCount,
|
|
searchPluralCount, countToPatterns, err);
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
|
|
if (formatters != NULL && formatters[style] != NULL) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if not found the pattern for this plural count at all,
|
|
// fall-back to plural count "other"
|
|
if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) {
|
|
// set default fall back the same as the resource in root
|
|
LocalPointer<MessageFormat> messageFormat;
|
|
const UChar *pattern = NULL;
|
|
if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) {
|
|
pattern = DEFAULT_PATTERN_FOR_SECOND;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) {
|
|
pattern = DEFAULT_PATTERN_FOR_MINUTE;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) {
|
|
pattern = DEFAULT_PATTERN_FOR_HOUR;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) {
|
|
pattern = DEFAULT_PATTERN_FOR_WEEK;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) {
|
|
pattern = DEFAULT_PATTERN_FOR_DAY;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) {
|
|
pattern = DEFAULT_PATTERN_FOR_MONTH;
|
|
} else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) {
|
|
pattern = DEFAULT_PATTERN_FOR_YEAR;
|
|
}
|
|
if (pattern != NULL) {
|
|
messageFormat.adoptInsteadAndCheckErrorCode(
|
|
new MessageFormat(UnicodeString(TRUE, pattern, -1), getLocale(err), err), err);
|
|
}
|
|
if (U_FAILURE(err)) {
|
|
return;
|
|
}
|
|
MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount);
|
|
if (formatters == NULL) {
|
|
LocalMemory<MessageFormat *> localFormatters (
|
|
(MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)));
|
|
if (localFormatters.isNull()) {
|
|
err = U_MEMORY_ALLOCATION_ERROR;
|
|
return;
|
|
}
|
|
formatters = localFormatters.getAlias();
|
|
formatters[UTMUTFMT_FULL_STYLE] = NULL;
|
|
formatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL;
|
|
countToPatterns->put(srcPluralCount, localFormatters.orphan(), err);
|
|
}
|
|
if (U_SUCCESS(err)) {
|
|
//delete formatters[style];
|
|
formatters[style] = messageFormat.orphan();
|
|
}
|
|
} else {
|
|
// fall back to rule "other", and search in parents
|
|
searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount,
|
|
gPluralCountOther, countToPatterns, err);
|
|
}
|
|
}
|
|
|
|
void
|
|
TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) {
|
|
if (setMeasureFormatLocale(locale, status)) {
|
|
setup(status);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){
|
|
if (U_FAILURE(status)) {
|
|
return;
|
|
}
|
|
adoptNumberFormat(format.clone(), status);
|
|
}
|
|
|
|
|
|
void
|
|
TimeUnitFormat::deleteHash(Hashtable* htable) {
|
|
int32_t pos = UHASH_FIRST;
|
|
const UHashElement* element = NULL;
|
|
if ( htable ) {
|
|
while ( (element = htable->nextElement(pos)) != NULL ) {
|
|
const UHashTok valueTok = element->value;
|
|
const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
|
|
delete value[UTMUTFMT_FULL_STYLE];
|
|
delete value[UTMUTFMT_ABBREVIATED_STYLE];
|
|
//delete[] value;
|
|
uprv_free(value);
|
|
}
|
|
}
|
|
delete htable;
|
|
}
|
|
|
|
|
|
void
|
|
TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) {
|
|
if ( U_FAILURE(status) ) {
|
|
return;
|
|
}
|
|
int32_t pos = UHASH_FIRST;
|
|
const UHashElement* element = NULL;
|
|
if ( source ) {
|
|
while ( (element = source->nextElement(pos)) != NULL ) {
|
|
const UHashTok keyTok = element->key;
|
|
const UnicodeString* key = (UnicodeString*)keyTok.pointer;
|
|
const UHashTok valueTok = element->value;
|
|
const MessageFormat** value = (const MessageFormat**)valueTok.pointer;
|
|
MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*));
|
|
newVal[0] = value[0]->clone();
|
|
newVal[1] = value[1]->clone();
|
|
target->put(UnicodeString(*key), newVal, status);
|
|
if ( U_FAILURE(status) ) {
|
|
delete newVal[0];
|
|
delete newVal[1];
|
|
uprv_free(newVal);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
U_CDECL_BEGIN
|
|
|
|
/**
|
|
* set hash table value comparator
|
|
*
|
|
* @param val1 one value in comparison
|
|
* @param val2 the other value in comparison
|
|
* @return TRUE if 2 values are the same, FALSE otherwise
|
|
*/
|
|
static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2);
|
|
|
|
static UBool
|
|
U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) {
|
|
const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer;
|
|
const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer;
|
|
return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1];
|
|
}
|
|
|
|
U_CDECL_END
|
|
|
|
Hashtable*
|
|
TimeUnitFormat::initHash(UErrorCode& status) {
|
|
if ( U_FAILURE(status) ) {
|
|
return NULL;
|
|
}
|
|
Hashtable* hTable;
|
|
if ( (hTable = new Hashtable(TRUE, status)) == NULL ) {
|
|
status = U_MEMORY_ALLOCATION_ERROR;
|
|
return NULL;
|
|
}
|
|
if ( U_FAILURE(status) ) {
|
|
delete hTable;
|
|
return NULL;
|
|
}
|
|
hTable->setValueComparator(tmutfmtHashTableValueComparator);
|
|
return hTable;
|
|
}
|
|
|
|
|
|
const char*
|
|
TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField,
|
|
UErrorCode& status) {
|
|
if (U_FAILURE(status)) {
|
|
return NULL;
|
|
}
|
|
switch (unitField) {
|
|
case TimeUnit::UTIMEUNIT_YEAR:
|
|
return gTimeUnitYear;
|
|
case TimeUnit::UTIMEUNIT_MONTH:
|
|
return gTimeUnitMonth;
|
|
case TimeUnit::UTIMEUNIT_DAY:
|
|
return gTimeUnitDay;
|
|
case TimeUnit::UTIMEUNIT_WEEK:
|
|
return gTimeUnitWeek;
|
|
case TimeUnit::UTIMEUNIT_HOUR:
|
|
return gTimeUnitHour;
|
|
case TimeUnit::UTIMEUNIT_MINUTE:
|
|
return gTimeUnitMinute;
|
|
case TimeUnit::UTIMEUNIT_SECOND:
|
|
return gTimeUnitSecond;
|
|
default:
|
|
status = U_ILLEGAL_ARGUMENT_ERROR;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
U_NAMESPACE_END
|
|
|
|
#endif
|