1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html |
3 | /* |
4 | ******************************************************************************* |
5 | * Copyright (C) 2008-2015, Google, International Business Machines Corporation |
6 | * and others. All Rights Reserved. |
7 | ******************************************************************************* |
8 | */ |
9 | |
10 | #include "unicode/tmutfmt.h" |
11 | |
12 | #if !UCONFIG_NO_FORMATTING |
13 | |
14 | #include "unicode/decimfmt.h" |
15 | #include "unicode/localpointer.h" |
16 | #include "plurrule_impl.h" |
17 | #include "uvector.h" |
18 | #include "charstr.h" |
19 | #include "cmemory.h" |
20 | #include "cstring.h" |
21 | #include "hash.h" |
22 | #include "uresimp.h" |
23 | #include "ureslocs.h" |
24 | #include "unicode/msgfmt.h" |
25 | #include "uassert.h" |
26 | |
27 | #define LEFT_CURLY_BRACKET ((UChar)0x007B) |
28 | #define RIGHT_CURLY_BRACKET ((UChar)0x007D) |
29 | #define SPACE ((UChar)0x0020) |
30 | #define DIGIT_ZERO ((UChar)0x0030) |
31 | #define LOW_S ((UChar)0x0073) |
32 | #define LOW_M ((UChar)0x006D) |
33 | #define LOW_I ((UChar)0x0069) |
34 | #define LOW_N ((UChar)0x006E) |
35 | #define LOW_H ((UChar)0x0068) |
36 | #define LOW_W ((UChar)0x0077) |
37 | #define LOW_D ((UChar)0x0064) |
38 | #define LOW_Y ((UChar)0x0079) |
39 | #define LOW_Z ((UChar)0x007A) |
40 | #define LOW_E ((UChar)0x0065) |
41 | #define LOW_R ((UChar)0x0072) |
42 | #define LOW_O ((UChar)0x006F) |
43 | #define LOW_N ((UChar)0x006E) |
44 | #define LOW_T ((UChar)0x0074) |
45 | |
46 | |
47 | //TODO: define in compile time |
48 | //#define TMUTFMT_DEBUG 1 |
49 | |
50 | #ifdef TMUTFMT_DEBUG |
51 | #include <iostream> |
52 | #endif |
53 | |
54 | U_NAMESPACE_BEGIN |
55 | |
56 | |
57 | |
58 | UOBJECT_DEFINE_RTTI_IMPLEMENTATION(TimeUnitFormat) |
59 | |
60 | static const char gUnitsTag[] = "units" ; |
61 | static const char gShortUnitsTag[] = "unitsShort" ; |
62 | static const char gTimeUnitYear[] = "year" ; |
63 | static const char gTimeUnitMonth[] = "month" ; |
64 | static const char gTimeUnitDay[] = "day" ; |
65 | static const char gTimeUnitWeek[] = "week" ; |
66 | static const char gTimeUnitHour[] = "hour" ; |
67 | static const char gTimeUnitMinute[] = "minute" ; |
68 | static const char gTimeUnitSecond[] = "second" ; |
69 | static const char gPluralCountOther[] = "other" ; |
70 | |
71 | static const UChar DEFAULT_PATTERN_FOR_SECOND[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_S, 0}; |
72 | static const UChar DEFAULT_PATTERN_FOR_MINUTE[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, LOW_I, LOW_N, 0}; |
73 | static const UChar DEFAULT_PATTERN_FOR_HOUR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_H, 0}; |
74 | static const UChar DEFAULT_PATTERN_FOR_WEEK[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_W, 0}; |
75 | static const UChar DEFAULT_PATTERN_FOR_DAY[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_D, 0}; |
76 | static const UChar DEFAULT_PATTERN_FOR_MONTH[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_M, 0}; |
77 | static const UChar DEFAULT_PATTERN_FOR_YEAR[] = {LEFT_CURLY_BRACKET, DIGIT_ZERO, RIGHT_CURLY_BRACKET, SPACE, LOW_Y, 0}; |
78 | |
79 | static const UChar PLURAL_COUNT_ZERO[] = {LOW_Z, LOW_E, LOW_R, LOW_O, 0}; |
80 | static const UChar PLURAL_COUNT_ONE[] = {LOW_O, LOW_N, LOW_E, 0}; |
81 | static const UChar PLURAL_COUNT_TWO[] = {LOW_T, LOW_W, LOW_O, 0}; |
82 | |
83 | TimeUnitFormat::TimeUnitFormat(UErrorCode& status) { |
84 | initMeasureFormat(Locale::getDefault(), UMEASFMT_WIDTH_WIDE, NULL, status); |
85 | create(UTMUTFMT_FULL_STYLE, status); |
86 | } |
87 | |
88 | |
89 | TimeUnitFormat::TimeUnitFormat(const Locale& locale, UErrorCode& status) { |
90 | initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status); |
91 | create(UTMUTFMT_FULL_STYLE, status); |
92 | } |
93 | |
94 | |
95 | TimeUnitFormat::TimeUnitFormat(const Locale& locale, UTimeUnitFormatStyle style, UErrorCode& status) { |
96 | switch (style) { |
97 | case UTMUTFMT_FULL_STYLE: |
98 | initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status); |
99 | break; |
100 | case UTMUTFMT_ABBREVIATED_STYLE: |
101 | initMeasureFormat(locale, UMEASFMT_WIDTH_SHORT, NULL, status); |
102 | break; |
103 | default: |
104 | initMeasureFormat(locale, UMEASFMT_WIDTH_WIDE, NULL, status); |
105 | break; |
106 | } |
107 | create(style, status); |
108 | } |
109 | |
110 | TimeUnitFormat::TimeUnitFormat(const TimeUnitFormat& other) |
111 | : MeasureFormat(other), |
112 | fStyle(other.fStyle) |
113 | { |
114 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
115 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
116 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
117 | UErrorCode status = U_ZERO_ERROR; |
118 | fTimeUnitToCountToPatterns[i] = initHash(status); |
119 | if (U_SUCCESS(status)) { |
120 | copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status); |
121 | } else { |
122 | delete fTimeUnitToCountToPatterns[i]; |
123 | fTimeUnitToCountToPatterns[i] = NULL; |
124 | } |
125 | } |
126 | } |
127 | |
128 | |
129 | TimeUnitFormat::~TimeUnitFormat() { |
130 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
131 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
132 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
133 | deleteHash(fTimeUnitToCountToPatterns[i]); |
134 | fTimeUnitToCountToPatterns[i] = NULL; |
135 | } |
136 | } |
137 | |
138 | |
139 | TimeUnitFormat* |
140 | TimeUnitFormat::clone() const { |
141 | return new TimeUnitFormat(*this); |
142 | } |
143 | |
144 | |
145 | TimeUnitFormat& |
146 | TimeUnitFormat::operator=(const TimeUnitFormat& other) { |
147 | if (this == &other) { |
148 | return *this; |
149 | } |
150 | MeasureFormat::operator=(other); |
151 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
152 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
153 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
154 | deleteHash(fTimeUnitToCountToPatterns[i]); |
155 | fTimeUnitToCountToPatterns[i] = NULL; |
156 | } |
157 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
158 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
159 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
160 | UErrorCode status = U_ZERO_ERROR; |
161 | fTimeUnitToCountToPatterns[i] = initHash(status); |
162 | if (U_SUCCESS(status)) { |
163 | copyHash(other.fTimeUnitToCountToPatterns[i], fTimeUnitToCountToPatterns[i], status); |
164 | } else { |
165 | delete fTimeUnitToCountToPatterns[i]; |
166 | fTimeUnitToCountToPatterns[i] = NULL; |
167 | } |
168 | } |
169 | fStyle = other.fStyle; |
170 | return *this; |
171 | } |
172 | |
173 | void |
174 | TimeUnitFormat::parseObject(const UnicodeString& source, |
175 | Formattable& result, |
176 | ParsePosition& pos) const { |
177 | Formattable resultNumber(0.0); |
178 | UBool withNumberFormat = false; |
179 | TimeUnit::UTimeUnitFields resultTimeUnit = TimeUnit::UTIMEUNIT_FIELD_COUNT; |
180 | int32_t oldPos = pos.getIndex(); |
181 | int32_t newPos = -1; |
182 | int32_t longestParseDistance = 0; |
183 | UnicodeString* countOfLongestMatch = NULL; |
184 | #ifdef TMUTFMT_DEBUG |
185 | char res[1000]; |
186 | source.extract(0, source.length(), res, "UTF-8" ); |
187 | std::cout << "parse source: " << res << "\n" ; |
188 | #endif |
189 | // parse by iterating through all available patterns |
190 | // and looking for the longest match. |
191 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
192 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
193 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
194 | Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i]; |
195 | int32_t elemPos = UHASH_FIRST; |
196 | const UHashElement* elem = NULL; |
197 | while ((elem = countToPatterns->nextElement(elemPos)) != NULL){ |
198 | const UHashTok keyTok = elem->key; |
199 | UnicodeString* count = (UnicodeString*)keyTok.pointer; |
200 | #ifdef TMUTFMT_DEBUG |
201 | count->extract(0, count->length(), res, "UTF-8" ); |
202 | std::cout << "parse plural count: " << res << "\n" ; |
203 | #endif |
204 | const UHashTok valueTok = elem->value; |
205 | // the value is a pair of MessageFormat* |
206 | MessageFormat** patterns = (MessageFormat**)valueTok.pointer; |
207 | for (UTimeUnitFormatStyle style = UTMUTFMT_FULL_STYLE; style < UTMUTFMT_FORMAT_STYLE_COUNT; |
208 | style = (UTimeUnitFormatStyle)(style + 1)) { |
209 | MessageFormat* pattern = patterns[style]; |
210 | pos.setErrorIndex(-1); |
211 | pos.setIndex(oldPos); |
212 | // see if we can parse |
213 | Formattable parsed; |
214 | pattern->parseObject(source, parsed, pos); |
215 | if (pos.getErrorIndex() != -1 || pos.getIndex() == oldPos) { |
216 | continue; |
217 | } |
218 | #ifdef TMUTFMT_DEBUG |
219 | std::cout << "parsed.getType: " << parsed.getType() << "\n" ; |
220 | #endif |
221 | Formattable tmpNumber(0.0); |
222 | if (pattern->getArgTypeCount() != 0) { |
223 | Formattable& temp = parsed[0]; |
224 | if (temp.getType() == Formattable::kString) { |
225 | UnicodeString tmpString; |
226 | UErrorCode pStatus = U_ZERO_ERROR; |
227 | getNumberFormatInternal().parse(temp.getString(tmpString), tmpNumber, pStatus); |
228 | if (U_FAILURE(pStatus)) { |
229 | continue; |
230 | } |
231 | } else if (temp.isNumeric()) { |
232 | tmpNumber = temp; |
233 | } else { |
234 | continue; |
235 | } |
236 | } |
237 | int32_t parseDistance = pos.getIndex() - oldPos; |
238 | if (parseDistance > longestParseDistance) { |
239 | if (pattern->getArgTypeCount() != 0) { |
240 | resultNumber = tmpNumber; |
241 | withNumberFormat = true; |
242 | } else { |
243 | withNumberFormat = false; |
244 | } |
245 | resultTimeUnit = i; |
246 | newPos = pos.getIndex(); |
247 | longestParseDistance = parseDistance; |
248 | countOfLongestMatch = count; |
249 | } |
250 | } |
251 | } |
252 | } |
253 | /* After find the longest match, parse the number. |
254 | * Result number could be null for the pattern without number pattern. |
255 | * such as unit pattern in Arabic. |
256 | * When result number is null, use plural rule to set the number. |
257 | */ |
258 | if (withNumberFormat == false && longestParseDistance != 0) { |
259 | // set the number using plurrual count |
260 | if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ZERO, 4)) { |
261 | resultNumber = Formattable(0.0); |
262 | } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_ONE, 3)) { |
263 | resultNumber = Formattable(1.0); |
264 | } else if (0 == countOfLongestMatch->compare(PLURAL_COUNT_TWO, 3)) { |
265 | resultNumber = Formattable(2.0); |
266 | } else { |
267 | // should not happen. |
268 | // TODO: how to handle? |
269 | resultNumber = Formattable(3.0); |
270 | } |
271 | } |
272 | if (longestParseDistance == 0) { |
273 | pos.setIndex(oldPos); |
274 | pos.setErrorIndex(0); |
275 | } else { |
276 | UErrorCode status = U_ZERO_ERROR; |
277 | LocalPointer<TimeUnitAmount> tmutamt(new TimeUnitAmount(resultNumber, resultTimeUnit, status), status); |
278 | if (U_SUCCESS(status)) { |
279 | result.adoptObject(tmutamt.orphan()); |
280 | pos.setIndex(newPos); |
281 | pos.setErrorIndex(-1); |
282 | } else { |
283 | pos.setIndex(oldPos); |
284 | pos.setErrorIndex(0); |
285 | } |
286 | } |
287 | } |
288 | |
289 | void |
290 | TimeUnitFormat::create(UTimeUnitFormatStyle style, UErrorCode& status) { |
291 | // fTimeUnitToCountToPatterns[] must have its elements initialized to NULL first |
292 | // before checking for failure status. |
293 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
294 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
295 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
296 | fTimeUnitToCountToPatterns[i] = NULL; |
297 | } |
298 | |
299 | if (U_FAILURE(status)) { |
300 | return; |
301 | } |
302 | if (style < UTMUTFMT_FULL_STYLE || style >= UTMUTFMT_FORMAT_STYLE_COUNT) { |
303 | status = U_ILLEGAL_ARGUMENT_ERROR; |
304 | return; |
305 | } |
306 | fStyle = style; |
307 | |
308 | //TODO: format() and parseObj() are const member functions, |
309 | //so, can not do lazy initialization in C++. |
310 | //setup has to be done in constructors. |
311 | //and here, the behavior is not consistent with Java. |
312 | //In Java, create an empty instance does not setup locale as |
313 | //default locale. If it followed by setNumberFormat(), |
314 | //in format(), the locale will set up as the locale in fNumberFormat. |
315 | //But in C++, this sets the locale as the default locale. |
316 | setup(status); |
317 | } |
318 | |
319 | void |
320 | TimeUnitFormat::setup(UErrorCode& err) { |
321 | initDataMembers(err); |
322 | |
323 | UVector pluralCounts(0, uhash_compareUnicodeString, 6, err); |
324 | LocalPointer<StringEnumeration> keywords(getPluralRules().getKeywords(err), err); |
325 | if (U_FAILURE(err)) { |
326 | return; |
327 | } |
328 | UnicodeString* pluralCount; |
329 | while ((pluralCount = const_cast<UnicodeString*>(keywords->snext(err))) != NULL) { |
330 | pluralCounts.addElement(pluralCount, err); |
331 | } |
332 | readFromCurrentLocale(UTMUTFMT_FULL_STYLE, gUnitsTag, pluralCounts, err); |
333 | checkConsistency(UTMUTFMT_FULL_STYLE, gUnitsTag, err); |
334 | readFromCurrentLocale(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, pluralCounts, err); |
335 | checkConsistency(UTMUTFMT_ABBREVIATED_STYLE, gShortUnitsTag, err); |
336 | } |
337 | |
338 | |
339 | void |
340 | TimeUnitFormat::initDataMembers(UErrorCode& err){ |
341 | if (U_FAILURE(err)) { |
342 | return; |
343 | } |
344 | for (TimeUnit::UTimeUnitFields i = TimeUnit::UTIMEUNIT_YEAR; |
345 | i < TimeUnit::UTIMEUNIT_FIELD_COUNT; |
346 | i = (TimeUnit::UTimeUnitFields)(i+1)) { |
347 | deleteHash(fTimeUnitToCountToPatterns[i]); |
348 | fTimeUnitToCountToPatterns[i] = NULL; |
349 | } |
350 | } |
351 | |
352 | struct TimeUnitFormatReadSink : public ResourceSink { |
353 | TimeUnitFormat *timeUnitFormatObj; |
354 | const UVector &pluralCounts; |
355 | UTimeUnitFormatStyle style; |
356 | UBool beenHere; |
357 | |
358 | TimeUnitFormatReadSink(TimeUnitFormat *timeUnitFormatObj, |
359 | const UVector &pluralCounts, UTimeUnitFormatStyle style) : |
360 | timeUnitFormatObj(timeUnitFormatObj), pluralCounts(pluralCounts), |
361 | style(style), beenHere(FALSE){} |
362 | |
363 | virtual ~TimeUnitFormatReadSink(); |
364 | |
365 | virtual void put(const char *key, ResourceValue &value, UBool, UErrorCode &errorCode) { |
366 | // Skip all put() calls except the first one -- discard all fallback data. |
367 | if (beenHere) { |
368 | return; |
369 | } else { |
370 | beenHere = TRUE; |
371 | } |
372 | |
373 | ResourceTable units = value.getTable(errorCode); |
374 | if (U_FAILURE(errorCode)) { return; } |
375 | |
376 | for (int32_t i = 0; units.getKeyAndValue(i, key, value); ++i) { |
377 | const char* timeUnitName = key; |
378 | if (timeUnitName == NULL) { |
379 | continue; |
380 | } |
381 | |
382 | TimeUnit::UTimeUnitFields timeUnitField = TimeUnit::UTIMEUNIT_FIELD_COUNT; |
383 | if ( uprv_strcmp(timeUnitName, gTimeUnitYear) == 0 ) { |
384 | timeUnitField = TimeUnit::UTIMEUNIT_YEAR; |
385 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitMonth) == 0 ) { |
386 | timeUnitField = TimeUnit::UTIMEUNIT_MONTH; |
387 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitDay) == 0 ) { |
388 | timeUnitField = TimeUnit::UTIMEUNIT_DAY; |
389 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitHour) == 0 ) { |
390 | timeUnitField = TimeUnit::UTIMEUNIT_HOUR; |
391 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitMinute) == 0 ) { |
392 | timeUnitField = TimeUnit::UTIMEUNIT_MINUTE; |
393 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitSecond) == 0 ) { |
394 | timeUnitField = TimeUnit::UTIMEUNIT_SECOND; |
395 | } else if ( uprv_strcmp(timeUnitName, gTimeUnitWeek) == 0 ) { |
396 | timeUnitField = TimeUnit::UTIMEUNIT_WEEK; |
397 | } else { |
398 | continue; |
399 | } |
400 | LocalPointer<Hashtable> localCountToPatterns; |
401 | Hashtable *countToPatterns = |
402 | timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField]; |
403 | if (countToPatterns == NULL) { |
404 | localCountToPatterns.adoptInsteadAndCheckErrorCode( |
405 | timeUnitFormatObj->initHash(errorCode), errorCode); |
406 | countToPatterns = localCountToPatterns.getAlias(); |
407 | if (U_FAILURE(errorCode)) { |
408 | return; |
409 | } |
410 | } |
411 | |
412 | ResourceTable countsToPatternTable = value.getTable(errorCode); |
413 | if (U_FAILURE(errorCode)) { |
414 | continue; |
415 | } |
416 | for (int32_t j = 0; countsToPatternTable.getKeyAndValue(j, key, value); ++j) { |
417 | errorCode = U_ZERO_ERROR; |
418 | UnicodeString pattern = value.getUnicodeString(errorCode); |
419 | if (U_FAILURE(errorCode)) { |
420 | continue; |
421 | } |
422 | UnicodeString pluralCountUniStr(key, -1, US_INV); |
423 | if (!pluralCounts.contains(&pluralCountUniStr)) { |
424 | continue; |
425 | } |
426 | LocalPointer<MessageFormat> messageFormat(new MessageFormat( |
427 | pattern, timeUnitFormatObj->getLocale(errorCode), errorCode), errorCode); |
428 | if (U_FAILURE(errorCode)) { |
429 | return; |
430 | } |
431 | MessageFormat** formatters = |
432 | (MessageFormat**)countToPatterns->get(pluralCountUniStr); |
433 | if (formatters == NULL) { |
434 | LocalMemory<MessageFormat *> localFormatters( |
435 | (MessageFormat **)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*))); |
436 | if (localFormatters.isNull()) { |
437 | errorCode = U_MEMORY_ALLOCATION_ERROR; |
438 | return; |
439 | } |
440 | localFormatters[UTMUTFMT_FULL_STYLE] = NULL; |
441 | localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; |
442 | countToPatterns->put(pluralCountUniStr, localFormatters.getAlias(), errorCode); |
443 | if (U_FAILURE(errorCode)) { |
444 | return; |
445 | } |
446 | formatters = localFormatters.orphan(); |
447 | } |
448 | formatters[style] = messageFormat.orphan(); |
449 | } |
450 | |
451 | if (timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] == NULL) { |
452 | timeUnitFormatObj->fTimeUnitToCountToPatterns[timeUnitField] = localCountToPatterns.orphan(); |
453 | } |
454 | } |
455 | } |
456 | |
457 | }; |
458 | |
459 | TimeUnitFormatReadSink::~TimeUnitFormatReadSink() {} |
460 | |
461 | void |
462 | TimeUnitFormat::readFromCurrentLocale(UTimeUnitFormatStyle style, const char* key, |
463 | const UVector& pluralCounts, UErrorCode& err) { |
464 | if (U_FAILURE(err)) { |
465 | return; |
466 | } |
467 | // fill timeUnitToCountToPatterns from resource file |
468 | // err is used to indicate wrong status except missing resource. |
469 | // status is an error code used in resource lookup. |
470 | // status does not affect "err". |
471 | UErrorCode status = U_ZERO_ERROR; |
472 | LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, getLocaleID(status), &status)); |
473 | |
474 | LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, NULL, &status)); |
475 | ures_getByKey(unitsRes.getAlias(), "duration" , unitsRes.getAlias(), &status); |
476 | if (U_FAILURE(status)) { |
477 | return; |
478 | } |
479 | |
480 | TimeUnitFormatReadSink sink(this, pluralCounts, style); |
481 | ures_getAllItemsWithFallback(unitsRes.getAlias(), "" , sink, status); |
482 | } |
483 | |
484 | void |
485 | TimeUnitFormat::checkConsistency(UTimeUnitFormatStyle style, const char* key, UErrorCode& err) { |
486 | if (U_FAILURE(err)) { |
487 | return; |
488 | } |
489 | // there should be patterns for each plural rule in each time unit. |
490 | // For each time unit, |
491 | // for each plural rule, following is unit pattern fall-back rule: |
492 | // ( for example: "one" hour ) |
493 | // look for its unit pattern in its locale tree. |
494 | // if pattern is not found in its own locale, such as de_DE, |
495 | // look for the pattern in its parent, such as de, |
496 | // keep looking till found or till root. |
497 | // if the pattern is not found in root either, |
498 | // fallback to plural count "other", |
499 | // look for the pattern of "other" in the locale tree: |
500 | // "de_DE" to "de" to "root". |
501 | // If not found, fall back to value of |
502 | // static variable DEFAULT_PATTERN_FOR_xxx, such as "{0} h". |
503 | // |
504 | // Following is consistency check to create pattern for each |
505 | // plural rule in each time unit using above fall-back rule. |
506 | // |
507 | LocalPointer<StringEnumeration> keywords( |
508 | getPluralRules().getKeywords(err), err); |
509 | const UnicodeString* pluralCount; |
510 | while (U_SUCCESS(err) && (pluralCount = keywords->snext(err)) != NULL) { |
511 | for (int32_t i = 0; i < TimeUnit::UTIMEUNIT_FIELD_COUNT; ++i) { |
512 | // for each time unit, |
513 | // get all the patterns for each plural rule in this locale. |
514 | Hashtable* countToPatterns = fTimeUnitToCountToPatterns[i]; |
515 | if ( countToPatterns == NULL ) { |
516 | fTimeUnitToCountToPatterns[i] = countToPatterns = initHash(err); |
517 | if (U_FAILURE(err)) { |
518 | return; |
519 | } |
520 | } |
521 | MessageFormat** formatters = (MessageFormat**)countToPatterns->get(*pluralCount); |
522 | if( formatters == NULL || formatters[style] == NULL ) { |
523 | // look through parents |
524 | const char* localeName = getLocaleID(err); |
525 | CharString pluralCountChars; |
526 | pluralCountChars.appendInvariantChars(*pluralCount, err); |
527 | searchInLocaleChain(style, key, localeName, |
528 | (TimeUnit::UTimeUnitFields)i, |
529 | *pluralCount, pluralCountChars.data(), |
530 | countToPatterns, err); |
531 | } |
532 | // TODO: what to do with U_FAILURE(err) at this point. |
533 | // As is, the outer loop continues to run, but does nothing. |
534 | } |
535 | } |
536 | } |
537 | |
538 | |
539 | |
540 | // srcPluralCount is the original plural count on which the pattern is |
541 | // searched for. |
542 | // searchPluralCount is the fallback plural count. |
543 | // For example, to search for pattern for ""one" hour", |
544 | // "one" is the srcPluralCount, |
545 | // if the pattern is not found even in root, fallback to |
546 | // using patterns of plural count "other", |
547 | // then, "other" is the searchPluralCount. |
548 | void |
549 | TimeUnitFormat::searchInLocaleChain(UTimeUnitFormatStyle style, const char* key, const char* localeName, |
550 | TimeUnit::UTimeUnitFields srcTimeUnitField, |
551 | const UnicodeString& srcPluralCount, |
552 | const char* searchPluralCount, |
553 | Hashtable* countToPatterns, |
554 | UErrorCode& err) { |
555 | if (U_FAILURE(err)) { |
556 | return; |
557 | } |
558 | UErrorCode status = U_ZERO_ERROR; |
559 | char parentLocale[ULOC_FULLNAME_CAPACITY]; |
560 | uprv_strcpy(parentLocale, localeName); |
561 | int32_t locNameLen; |
562 | U_ASSERT(countToPatterns != NULL); |
563 | while ((locNameLen = uloc_getParent(parentLocale, parentLocale, |
564 | ULOC_FULLNAME_CAPACITY, &status)) >= 0){ |
565 | // look for pattern for srcPluralCount in locale tree |
566 | LocalUResourceBundlePointer rb(ures_open(U_ICUDATA_UNIT, parentLocale, &status)); |
567 | LocalUResourceBundlePointer unitsRes(ures_getByKey(rb.getAlias(), key, NULL, &status)); |
568 | const char* timeUnitName = getTimeUnitName(srcTimeUnitField, status); |
569 | LocalUResourceBundlePointer countsToPatternRB(ures_getByKey(unitsRes.getAlias(), timeUnitName, NULL, &status)); |
570 | const UChar* pattern; |
571 | int32_t ptLength; |
572 | pattern = ures_getStringByKeyWithFallback(countsToPatternRB.getAlias(), searchPluralCount, &ptLength, &status); |
573 | if (U_SUCCESS(status)) { |
574 | //found |
575 | LocalPointer<MessageFormat> messageFormat( |
576 | new MessageFormat(UnicodeString(TRUE, pattern, ptLength), getLocale(err), err), err); |
577 | if (U_FAILURE(err)) { |
578 | return; |
579 | } |
580 | MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); |
581 | if (formatters == NULL) { |
582 | LocalMemory<MessageFormat *> localFormatters( |
583 | (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*))); |
584 | formatters = localFormatters.getAlias(); |
585 | localFormatters[UTMUTFMT_FULL_STYLE] = NULL; |
586 | localFormatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; |
587 | countToPatterns->put(srcPluralCount, localFormatters.orphan(), err); |
588 | if (U_FAILURE(err)) { |
589 | return; |
590 | } |
591 | } |
592 | //delete formatters[style]; |
593 | formatters[style] = messageFormat.orphan(); |
594 | return; |
595 | } |
596 | status = U_ZERO_ERROR; |
597 | if (locNameLen == 0) { |
598 | break; |
599 | } |
600 | } |
601 | |
602 | // if no unitsShort resource was found even after fallback to root locale |
603 | // then search the units resource fallback from the current level to root |
604 | if ( locNameLen == 0 && uprv_strcmp(key, gShortUnitsTag) == 0) { |
605 | #ifdef TMUTFMT_DEBUG |
606 | std::cout << "loop into searchInLocaleChain since Short-Long-Alternative \n" ; |
607 | #endif |
608 | CharString pLocale(localeName, -1, err); |
609 | // Add an underscore at the tail of locale name, |
610 | // so that searchInLocaleChain will check the current locale before falling back |
611 | pLocale.append('_', err); |
612 | searchInLocaleChain(style, gUnitsTag, pLocale.data(), srcTimeUnitField, srcPluralCount, |
613 | searchPluralCount, countToPatterns, err); |
614 | if (U_FAILURE(err)) { |
615 | return; |
616 | } |
617 | MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); |
618 | if (formatters != NULL && formatters[style] != NULL) { |
619 | return; |
620 | } |
621 | } |
622 | |
623 | // if not found the pattern for this plural count at all, |
624 | // fall-back to plural count "other" |
625 | if ( uprv_strcmp(searchPluralCount, gPluralCountOther) == 0 ) { |
626 | // set default fall back the same as the resource in root |
627 | LocalPointer<MessageFormat> messageFormat; |
628 | const UChar *pattern = NULL; |
629 | if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_SECOND ) { |
630 | pattern = DEFAULT_PATTERN_FOR_SECOND; |
631 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MINUTE ) { |
632 | pattern = DEFAULT_PATTERN_FOR_MINUTE; |
633 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_HOUR ) { |
634 | pattern = DEFAULT_PATTERN_FOR_HOUR; |
635 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_WEEK ) { |
636 | pattern = DEFAULT_PATTERN_FOR_WEEK; |
637 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_DAY ) { |
638 | pattern = DEFAULT_PATTERN_FOR_DAY; |
639 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_MONTH ) { |
640 | pattern = DEFAULT_PATTERN_FOR_MONTH; |
641 | } else if ( srcTimeUnitField == TimeUnit::UTIMEUNIT_YEAR ) { |
642 | pattern = DEFAULT_PATTERN_FOR_YEAR; |
643 | } |
644 | if (pattern != NULL) { |
645 | messageFormat.adoptInsteadAndCheckErrorCode( |
646 | new MessageFormat(UnicodeString(TRUE, pattern, -1), getLocale(err), err), err); |
647 | } |
648 | if (U_FAILURE(err)) { |
649 | return; |
650 | } |
651 | MessageFormat** formatters = (MessageFormat**)countToPatterns->get(srcPluralCount); |
652 | if (formatters == NULL) { |
653 | LocalMemory<MessageFormat *> localFormatters ( |
654 | (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*))); |
655 | if (localFormatters.isNull()) { |
656 | err = U_MEMORY_ALLOCATION_ERROR; |
657 | return; |
658 | } |
659 | formatters = localFormatters.getAlias(); |
660 | formatters[UTMUTFMT_FULL_STYLE] = NULL; |
661 | formatters[UTMUTFMT_ABBREVIATED_STYLE] = NULL; |
662 | countToPatterns->put(srcPluralCount, localFormatters.orphan(), err); |
663 | } |
664 | if (U_SUCCESS(err)) { |
665 | //delete formatters[style]; |
666 | formatters[style] = messageFormat.orphan(); |
667 | } |
668 | } else { |
669 | // fall back to rule "other", and search in parents |
670 | searchInLocaleChain(style, key, localeName, srcTimeUnitField, srcPluralCount, |
671 | gPluralCountOther, countToPatterns, err); |
672 | } |
673 | } |
674 | |
675 | void |
676 | TimeUnitFormat::setLocale(const Locale& locale, UErrorCode& status) { |
677 | if (setMeasureFormatLocale(locale, status)) { |
678 | setup(status); |
679 | } |
680 | } |
681 | |
682 | |
683 | void |
684 | TimeUnitFormat::setNumberFormat(const NumberFormat& format, UErrorCode& status){ |
685 | if (U_FAILURE(status)) { |
686 | return; |
687 | } |
688 | adoptNumberFormat(format.clone(), status); |
689 | } |
690 | |
691 | |
692 | void |
693 | TimeUnitFormat::deleteHash(Hashtable* htable) { |
694 | int32_t pos = UHASH_FIRST; |
695 | const UHashElement* element = NULL; |
696 | if ( htable ) { |
697 | while ( (element = htable->nextElement(pos)) != NULL ) { |
698 | const UHashTok valueTok = element->value; |
699 | const MessageFormat** value = (const MessageFormat**)valueTok.pointer; |
700 | delete value[UTMUTFMT_FULL_STYLE]; |
701 | delete value[UTMUTFMT_ABBREVIATED_STYLE]; |
702 | //delete[] value; |
703 | uprv_free(value); |
704 | } |
705 | } |
706 | delete htable; |
707 | } |
708 | |
709 | |
710 | void |
711 | TimeUnitFormat::copyHash(const Hashtable* source, Hashtable* target, UErrorCode& status) { |
712 | if ( U_FAILURE(status) ) { |
713 | return; |
714 | } |
715 | int32_t pos = UHASH_FIRST; |
716 | const UHashElement* element = NULL; |
717 | if ( source ) { |
718 | while ( (element = source->nextElement(pos)) != NULL ) { |
719 | const UHashTok keyTok = element->key; |
720 | const UnicodeString* key = (UnicodeString*)keyTok.pointer; |
721 | const UHashTok valueTok = element->value; |
722 | const MessageFormat** value = (const MessageFormat**)valueTok.pointer; |
723 | MessageFormat** newVal = (MessageFormat**)uprv_malloc(UTMUTFMT_FORMAT_STYLE_COUNT*sizeof(MessageFormat*)); |
724 | newVal[0] = value[0]->clone(); |
725 | newVal[1] = value[1]->clone(); |
726 | target->put(UnicodeString(*key), newVal, status); |
727 | if ( U_FAILURE(status) ) { |
728 | delete newVal[0]; |
729 | delete newVal[1]; |
730 | uprv_free(newVal); |
731 | return; |
732 | } |
733 | } |
734 | } |
735 | } |
736 | |
737 | |
738 | U_CDECL_BEGIN |
739 | |
740 | /** |
741 | * set hash table value comparator |
742 | * |
743 | * @param val1 one value in comparison |
744 | * @param val2 the other value in comparison |
745 | * @return TRUE if 2 values are the same, FALSE otherwise |
746 | */ |
747 | static UBool U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2); |
748 | |
749 | static UBool |
750 | U_CALLCONV tmutfmtHashTableValueComparator(UHashTok val1, UHashTok val2) { |
751 | const MessageFormat** pattern1 = (const MessageFormat**)val1.pointer; |
752 | const MessageFormat** pattern2 = (const MessageFormat**)val2.pointer; |
753 | return *pattern1[0] == *pattern2[0] && *pattern1[1] == *pattern2[1]; |
754 | } |
755 | |
756 | U_CDECL_END |
757 | |
758 | Hashtable* |
759 | TimeUnitFormat::initHash(UErrorCode& status) { |
760 | if ( U_FAILURE(status) ) { |
761 | return NULL; |
762 | } |
763 | Hashtable* hTable; |
764 | if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { |
765 | status = U_MEMORY_ALLOCATION_ERROR; |
766 | return NULL; |
767 | } |
768 | if ( U_FAILURE(status) ) { |
769 | delete hTable; |
770 | return NULL; |
771 | } |
772 | hTable->setValueComparator(tmutfmtHashTableValueComparator); |
773 | return hTable; |
774 | } |
775 | |
776 | |
777 | const char* |
778 | TimeUnitFormat::getTimeUnitName(TimeUnit::UTimeUnitFields unitField, |
779 | UErrorCode& status) { |
780 | if (U_FAILURE(status)) { |
781 | return NULL; |
782 | } |
783 | switch (unitField) { |
784 | case TimeUnit::UTIMEUNIT_YEAR: |
785 | return gTimeUnitYear; |
786 | case TimeUnit::UTIMEUNIT_MONTH: |
787 | return gTimeUnitMonth; |
788 | case TimeUnit::UTIMEUNIT_DAY: |
789 | return gTimeUnitDay; |
790 | case TimeUnit::UTIMEUNIT_WEEK: |
791 | return gTimeUnitWeek; |
792 | case TimeUnit::UTIMEUNIT_HOUR: |
793 | return gTimeUnitHour; |
794 | case TimeUnit::UTIMEUNIT_MINUTE: |
795 | return gTimeUnitMinute; |
796 | case TimeUnit::UTIMEUNIT_SECOND: |
797 | return gTimeUnitSecond; |
798 | default: |
799 | status = U_ILLEGAL_ARGUMENT_ERROR; |
800 | return NULL; |
801 | } |
802 | } |
803 | |
804 | U_NAMESPACE_END |
805 | |
806 | #endif |
807 | |