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