intl/icu/source/i18n/compactdecimalformat.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /*
michael@0 2 *******************************************************************************
michael@0 3 * Copyright (C) 1997-2012, International Business Machines Corporation and *
michael@0 4 * others. All Rights Reserved. *
michael@0 5 *******************************************************************************
michael@0 6 *
michael@0 7 * File COMPACTDECIMALFORMAT.CPP
michael@0 8 *
michael@0 9 ********************************************************************************
michael@0 10 */
michael@0 11 #include "unicode/utypes.h"
michael@0 12
michael@0 13 #if !UCONFIG_NO_FORMATTING
michael@0 14
michael@0 15 #include "charstr.h"
michael@0 16 #include "cstring.h"
michael@0 17 #include "digitlst.h"
michael@0 18 #include "mutex.h"
michael@0 19 #include "unicode/compactdecimalformat.h"
michael@0 20 #include "unicode/numsys.h"
michael@0 21 #include "unicode/plurrule.h"
michael@0 22 #include "unicode/ures.h"
michael@0 23 #include "ucln_in.h"
michael@0 24 #include "uhash.h"
michael@0 25 #include "umutex.h"
michael@0 26 #include "unicode/ures.h"
michael@0 27 #include "uresimp.h"
michael@0 28
michael@0 29 #define LENGTHOF(array) (int32_t)(sizeof(array) / sizeof((array)[0]))
michael@0 30
michael@0 31 // Maps locale name to CDFLocaleData struct.
michael@0 32 static UHashtable* gCompactDecimalData = NULL;
michael@0 33 static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER;
michael@0 34
michael@0 35 U_NAMESPACE_BEGIN
michael@0 36
michael@0 37 static const int32_t MAX_DIGITS = 15;
michael@0 38 static const char gOther[] = "other";
michael@0 39 static const char gLatnTag[] = "latn";
michael@0 40 static const char gNumberElementsTag[] = "NumberElements";
michael@0 41 static const char gDecimalFormatTag[] = "decimalFormat";
michael@0 42 static const char gPatternsShort[] = "patternsShort";
michael@0 43 static const char gPatternsLong[] = "patternsLong";
michael@0 44 static const char gRoot[] = "root";
michael@0 45
michael@0 46 static const UChar u_0 = 0x30;
michael@0 47 static const UChar u_apos = 0x27;
michael@0 48
michael@0 49 static const UChar kZero[] = {u_0};
michael@0 50
michael@0 51 // Used to unescape single quotes.
michael@0 52 enum QuoteState {
michael@0 53 OUTSIDE,
michael@0 54 INSIDE_EMPTY,
michael@0 55 INSIDE_FULL
michael@0 56 };
michael@0 57
michael@0 58 enum FallbackFlags {
michael@0 59 ANY = 0,
michael@0 60 MUST = 1,
michael@0 61 NOT_ROOT = 2
michael@0 62 // Next one will be 4 then 6 etc.
michael@0 63 };
michael@0 64
michael@0 65
michael@0 66 // CDFUnit represents a prefix-suffix pair for a particular variant
michael@0 67 // and log10 value.
michael@0 68 struct CDFUnit : public UMemory {
michael@0 69 UnicodeString prefix;
michael@0 70 UnicodeString suffix;
michael@0 71 inline CDFUnit() : prefix(), suffix() {
michael@0 72 prefix.setToBogus();
michael@0 73 }
michael@0 74 inline ~CDFUnit() {}
michael@0 75 inline UBool isSet() const {
michael@0 76 return !prefix.isBogus();
michael@0 77 }
michael@0 78 inline void markAsSet() {
michael@0 79 prefix.remove();
michael@0 80 }
michael@0 81 };
michael@0 82
michael@0 83 // CDFLocaleStyleData contains formatting data for a particular locale
michael@0 84 // and style.
michael@0 85 class CDFLocaleStyleData : public UMemory {
michael@0 86 public:
michael@0 87 // What to divide by for each log10 value when formatting. These values
michael@0 88 // will be powers of 10. For English, would be:
michael@0 89 // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ...
michael@0 90 double divisors[MAX_DIGITS];
michael@0 91 // Maps plural variants to CDFUnit[MAX_DIGITS] arrays.
michael@0 92 // To format a number x,
michael@0 93 // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]).
michael@0 94 // Compute the plural variant for displayNum
michael@0 95 // (e.g zero, one, two, few, many, other).
michael@0 96 // Compute cdfUnits = unitsByVariant[pluralVariant].
michael@0 97 // Prefix and suffix to use at cdfUnits[log10(x)]
michael@0 98 UHashtable* unitsByVariant;
michael@0 99 inline CDFLocaleStyleData() : unitsByVariant(NULL) {}
michael@0 100 ~CDFLocaleStyleData();
michael@0 101 // Init initializes this object.
michael@0 102 void Init(UErrorCode& status);
michael@0 103 inline UBool isBogus() const {
michael@0 104 return unitsByVariant == NULL;
michael@0 105 }
michael@0 106 void setToBogus();
michael@0 107 private:
michael@0 108 CDFLocaleStyleData(const CDFLocaleStyleData&);
michael@0 109 CDFLocaleStyleData& operator=(const CDFLocaleStyleData&);
michael@0 110 };
michael@0 111
michael@0 112 // CDFLocaleData contains formatting data for a particular locale.
michael@0 113 struct CDFLocaleData : public UMemory {
michael@0 114 CDFLocaleStyleData shortData;
michael@0 115 CDFLocaleStyleData longData;
michael@0 116 inline CDFLocaleData() : shortData(), longData() { }
michael@0 117 inline ~CDFLocaleData() { }
michael@0 118 // Init initializes this object.
michael@0 119 void Init(UErrorCode& status);
michael@0 120 };
michael@0 121
michael@0 122 U_NAMESPACE_END
michael@0 123
michael@0 124 U_CDECL_BEGIN
michael@0 125
michael@0 126 static UBool U_CALLCONV cdf_cleanup(void) {
michael@0 127 if (gCompactDecimalData != NULL) {
michael@0 128 uhash_close(gCompactDecimalData);
michael@0 129 gCompactDecimalData = NULL;
michael@0 130 }
michael@0 131 return TRUE;
michael@0 132 }
michael@0 133
michael@0 134 static void U_CALLCONV deleteCDFUnits(void* ptr) {
michael@0 135 delete [] (icu::CDFUnit*) ptr;
michael@0 136 }
michael@0 137
michael@0 138 static void U_CALLCONV deleteCDFLocaleData(void* ptr) {
michael@0 139 delete (icu::CDFLocaleData*) ptr;
michael@0 140 }
michael@0 141
michael@0 142 U_CDECL_END
michael@0 143
michael@0 144 U_NAMESPACE_BEGIN
michael@0 145
michael@0 146 static UBool divisors_equal(const double* lhs, const double* rhs);
michael@0 147 static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status);
michael@0 148
michael@0 149 static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status);
michael@0 150 static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status);
michael@0 151 static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status);
michael@0 152 static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status);
michael@0 153 static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status);
michael@0 154 static UBool isRoot(const UResourceBundle* rb, UErrorCode& status);
michael@0 155 static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status);
michael@0 156 static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status);
michael@0 157 static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status);
michael@0 158 static UBool onlySpaces(UnicodeString u);
michael@0 159 static void fixQuotes(UnicodeString& s);
michael@0 160 static void fillInMissing(CDFLocaleStyleData* result);
michael@0 161 static int32_t computeLog10(double x, UBool inRange);
michael@0 162 static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status);
michael@0 163 static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value);
michael@0 164
michael@0 165 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat)
michael@0 166
michael@0 167 CompactDecimalFormat::CompactDecimalFormat(
michael@0 168 const DecimalFormat& decimalFormat,
michael@0 169 const UHashtable* unitsByVariant,
michael@0 170 const double* divisors,
michael@0 171 PluralRules* pluralRules)
michael@0 172 : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) {
michael@0 173 }
michael@0 174
michael@0 175 CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source)
michael@0 176 : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) {
michael@0 177 }
michael@0 178
michael@0 179 CompactDecimalFormat* U_EXPORT2
michael@0 180 CompactDecimalFormat::createInstance(
michael@0 181 const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) {
michael@0 182 LocalPointer<DecimalFormat> decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status));
michael@0 183 if (U_FAILURE(status)) {
michael@0 184 return NULL;
michael@0 185 }
michael@0 186 LocalPointer<PluralRules> pluralRules(PluralRules::forLocale(inLocale, status));
michael@0 187 if (U_FAILURE(status)) {
michael@0 188 return NULL;
michael@0 189 }
michael@0 190 const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status);
michael@0 191 if (U_FAILURE(status)) {
michael@0 192 return NULL;
michael@0 193 }
michael@0 194 CompactDecimalFormat* result =
michael@0 195 new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias());
michael@0 196 if (result == NULL) {
michael@0 197 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 198 return NULL;
michael@0 199 }
michael@0 200 pluralRules.orphan();
michael@0 201 result->setMaximumSignificantDigits(3);
michael@0 202 result->setSignificantDigitsUsed(TRUE);
michael@0 203 result->setGroupingUsed(FALSE);
michael@0 204 return result;
michael@0 205 }
michael@0 206
michael@0 207 CompactDecimalFormat&
michael@0 208 CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) {
michael@0 209 if (this != &rhs) {
michael@0 210 DecimalFormat::operator=(rhs);
michael@0 211 _unitsByVariant = rhs._unitsByVariant;
michael@0 212 _divisors = rhs._divisors;
michael@0 213 delete _pluralRules;
michael@0 214 _pluralRules = rhs._pluralRules->clone();
michael@0 215 }
michael@0 216 return *this;
michael@0 217 }
michael@0 218
michael@0 219 CompactDecimalFormat::~CompactDecimalFormat() {
michael@0 220 delete _pluralRules;
michael@0 221 }
michael@0 222
michael@0 223
michael@0 224 Format*
michael@0 225 CompactDecimalFormat::clone(void) const {
michael@0 226 return new CompactDecimalFormat(*this);
michael@0 227 }
michael@0 228
michael@0 229 UBool
michael@0 230 CompactDecimalFormat::operator==(const Format& that) const {
michael@0 231 if (this == &that) {
michael@0 232 return TRUE;
michael@0 233 }
michael@0 234 return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that));
michael@0 235 }
michael@0 236
michael@0 237 UBool
michael@0 238 CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const {
michael@0 239 return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules);
michael@0 240 }
michael@0 241
michael@0 242 UnicodeString&
michael@0 243 CompactDecimalFormat::format(
michael@0 244 double number,
michael@0 245 UnicodeString& appendTo,
michael@0 246 FieldPosition& pos) const {
michael@0 247 DigitList orig, rounded;
michael@0 248 orig.set(number);
michael@0 249 UBool isNegative;
michael@0 250 UErrorCode status = U_ZERO_ERROR;
michael@0 251 _round(orig, rounded, isNegative, status);
michael@0 252 if (U_FAILURE(status)) {
michael@0 253 return appendTo;
michael@0 254 }
michael@0 255 double roundedDouble = rounded.getDouble();
michael@0 256 if (isNegative) {
michael@0 257 roundedDouble = -roundedDouble;
michael@0 258 }
michael@0 259 int32_t baseIdx = computeLog10(roundedDouble, TRUE);
michael@0 260 double numberToFormat = roundedDouble / _divisors[baseIdx];
michael@0 261 UnicodeString variant = _pluralRules->select(numberToFormat);
michael@0 262 if (isNegative) {
michael@0 263 numberToFormat = -numberToFormat;
michael@0 264 }
michael@0 265 const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx);
michael@0 266 appendTo += unit->prefix;
michael@0 267 DecimalFormat::format(numberToFormat, appendTo, pos);
michael@0 268 appendTo += unit->suffix;
michael@0 269 return appendTo;
michael@0 270 }
michael@0 271
michael@0 272 UnicodeString&
michael@0 273 CompactDecimalFormat::format(
michael@0 274 double /* number */,
michael@0 275 UnicodeString& appendTo,
michael@0 276 FieldPositionIterator* /* posIter */,
michael@0 277 UErrorCode& status) const {
michael@0 278 status = U_UNSUPPORTED_ERROR;
michael@0 279 return appendTo;
michael@0 280 }
michael@0 281
michael@0 282 UnicodeString&
michael@0 283 CompactDecimalFormat::format(
michael@0 284 int64_t number,
michael@0 285 UnicodeString& appendTo,
michael@0 286 FieldPosition& pos) const {
michael@0 287 return format((double) number, appendTo, pos);
michael@0 288 }
michael@0 289
michael@0 290 UnicodeString&
michael@0 291 CompactDecimalFormat::format(
michael@0 292 int64_t /* number */,
michael@0 293 UnicodeString& appendTo,
michael@0 294 FieldPositionIterator* /* posIter */,
michael@0 295 UErrorCode& status) const {
michael@0 296 status = U_UNSUPPORTED_ERROR;
michael@0 297 return appendTo;
michael@0 298 }
michael@0 299
michael@0 300 UnicodeString&
michael@0 301 CompactDecimalFormat::format(
michael@0 302 const StringPiece& /* number */,
michael@0 303 UnicodeString& appendTo,
michael@0 304 FieldPositionIterator* /* posIter */,
michael@0 305 UErrorCode& status) const {
michael@0 306 status = U_UNSUPPORTED_ERROR;
michael@0 307 return appendTo;
michael@0 308 }
michael@0 309
michael@0 310 UnicodeString&
michael@0 311 CompactDecimalFormat::format(
michael@0 312 const DigitList& /* number */,
michael@0 313 UnicodeString& appendTo,
michael@0 314 FieldPositionIterator* /* posIter */,
michael@0 315 UErrorCode& status) const {
michael@0 316 status = U_UNSUPPORTED_ERROR;
michael@0 317 return appendTo;
michael@0 318 }
michael@0 319
michael@0 320 UnicodeString&
michael@0 321 CompactDecimalFormat::format(const DigitList& /* number */,
michael@0 322 UnicodeString& appendTo,
michael@0 323 FieldPosition& /* pos */,
michael@0 324 UErrorCode& status) const {
michael@0 325 status = U_UNSUPPORTED_ERROR;
michael@0 326 return appendTo;
michael@0 327 }
michael@0 328
michael@0 329 void
michael@0 330 CompactDecimalFormat::parse(
michael@0 331 const UnicodeString& /* text */,
michael@0 332 Formattable& /* result */,
michael@0 333 ParsePosition& /* parsePosition */) const {
michael@0 334 }
michael@0 335
michael@0 336 void
michael@0 337 CompactDecimalFormat::parse(
michael@0 338 const UnicodeString& /* text */,
michael@0 339 Formattable& /* result */,
michael@0 340 UErrorCode& status) const {
michael@0 341 status = U_UNSUPPORTED_ERROR;
michael@0 342 }
michael@0 343
michael@0 344 CurrencyAmount*
michael@0 345 CompactDecimalFormat::parseCurrency(
michael@0 346 const UnicodeString& /* text */,
michael@0 347 ParsePosition& /* pos */) const {
michael@0 348 return NULL;
michael@0 349 }
michael@0 350
michael@0 351 void CDFLocaleStyleData::Init(UErrorCode& status) {
michael@0 352 if (unitsByVariant != NULL) {
michael@0 353 return;
michael@0 354 }
michael@0 355 unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
michael@0 356 if (U_FAILURE(status)) {
michael@0 357 return;
michael@0 358 }
michael@0 359 uhash_setKeyDeleter(unitsByVariant, uprv_free);
michael@0 360 uhash_setValueDeleter(unitsByVariant, deleteCDFUnits);
michael@0 361 }
michael@0 362
michael@0 363 CDFLocaleStyleData::~CDFLocaleStyleData() {
michael@0 364 setToBogus();
michael@0 365 }
michael@0 366
michael@0 367 void CDFLocaleStyleData::setToBogus() {
michael@0 368 if (unitsByVariant != NULL) {
michael@0 369 uhash_close(unitsByVariant);
michael@0 370 unitsByVariant = NULL;
michael@0 371 }
michael@0 372 }
michael@0 373
michael@0 374 void CDFLocaleData::Init(UErrorCode& status) {
michael@0 375 shortData.Init(status);
michael@0 376 if (U_FAILURE(status)) {
michael@0 377 return;
michael@0 378 }
michael@0 379 longData.Init(status);
michael@0 380 }
michael@0 381
michael@0 382 // Helper method for operator=
michael@0 383 static UBool divisors_equal(const double* lhs, const double* rhs) {
michael@0 384 for (int32_t i = 0; i < MAX_DIGITS; ++i) {
michael@0 385 if (lhs[i] != rhs[i]) {
michael@0 386 return FALSE;
michael@0 387 }
michael@0 388 }
michael@0 389 return TRUE;
michael@0 390 }
michael@0 391
michael@0 392 // getCDFLocaleStyleData returns pointer to formatting data for given locale and
michael@0 393 // style within the global cache. On cache miss, getCDFLocaleStyleData loads
michael@0 394 // the data from CLDR into the global cache before returning the pointer. If a
michael@0 395 // UNUM_LONG data is requested for a locale, and that locale does not have
michael@0 396 // UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for
michael@0 397 // that locale.
michael@0 398 static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) {
michael@0 399 if (U_FAILURE(status)) {
michael@0 400 return NULL;
michael@0 401 }
michael@0 402 CDFLocaleData* result = NULL;
michael@0 403 const char* key = inLocale.getName();
michael@0 404 {
michael@0 405 Mutex lock(&gCompactDecimalMetaLock);
michael@0 406 if (gCompactDecimalData == NULL) {
michael@0 407 gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status);
michael@0 408 if (U_FAILURE(status)) {
michael@0 409 return NULL;
michael@0 410 }
michael@0 411 uhash_setKeyDeleter(gCompactDecimalData, uprv_free);
michael@0 412 uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData);
michael@0 413 ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup);
michael@0 414 } else {
michael@0 415 result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key);
michael@0 416 }
michael@0 417 }
michael@0 418 if (result != NULL) {
michael@0 419 return extractDataByStyleEnum(*result, style, status);
michael@0 420 }
michael@0 421
michael@0 422 result = loadCDFLocaleData(inLocale, status);
michael@0 423 if (U_FAILURE(status)) {
michael@0 424 return NULL;
michael@0 425 }
michael@0 426
michael@0 427 {
michael@0 428 Mutex lock(&gCompactDecimalMetaLock);
michael@0 429 CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key);
michael@0 430 if (temp != NULL) {
michael@0 431 delete result;
michael@0 432 result = temp;
michael@0 433 } else {
michael@0 434 uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status);
michael@0 435 if (U_FAILURE(status)) {
michael@0 436 return NULL;
michael@0 437 }
michael@0 438 }
michael@0 439 }
michael@0 440 return extractDataByStyleEnum(*result, style, status);
michael@0 441 }
michael@0 442
michael@0 443 static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) {
michael@0 444 switch (style) {
michael@0 445 case UNUM_SHORT:
michael@0 446 return &data.shortData;
michael@0 447 case UNUM_LONG:
michael@0 448 if (!data.longData.isBogus()) {
michael@0 449 return &data.longData;
michael@0 450 }
michael@0 451 return &data.shortData;
michael@0 452 default:
michael@0 453 status = U_ILLEGAL_ARGUMENT_ERROR;
michael@0 454 return NULL;
michael@0 455 }
michael@0 456 }
michael@0 457
michael@0 458 // loadCDFLocaleData loads formatting data from CLDR for a given locale. The
michael@0 459 // caller owns the returned pointer.
michael@0 460 static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) {
michael@0 461 if (U_FAILURE(status)) {
michael@0 462 return NULL;
michael@0 463 }
michael@0 464 CDFLocaleData* result = new CDFLocaleData;
michael@0 465 if (result == NULL) {
michael@0 466 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 467 return NULL;
michael@0 468 }
michael@0 469 result->Init(status);
michael@0 470 if (U_FAILURE(status)) {
michael@0 471 delete result;
michael@0 472 return NULL;
michael@0 473 }
michael@0 474
michael@0 475 initCDFLocaleData(inLocale, result, status);
michael@0 476 if (U_FAILURE(status)) {
michael@0 477 delete result;
michael@0 478 return NULL;
michael@0 479 }
michael@0 480 return result;
michael@0 481 }
michael@0 482
michael@0 483 // initCDFLocaleData initializes result with data from CLDR.
michael@0 484 // inLocale is the locale, the CLDR data is stored in result.
michael@0 485 // We load the UNUM_SHORT and UNUM_LONG data looking first in local numbering
michael@0 486 // system and not including root locale in fallback. Next we try in the latn
michael@0 487 // numbering system where we fallback all the way to root. If we don't find
michael@0 488 // UNUM_SHORT data in these three places, we report an error. If we find
michael@0 489 // UNUM_SHORT data before finding UNUM_LONG data we make UNUM_LONG data fall
michael@0 490 // back to UNUM_SHORT data.
michael@0 491 static void initCDFLocaleData(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) {
michael@0 492 LocalPointer<NumberingSystem> ns(NumberingSystem::createInstance(inLocale, status));
michael@0 493 if (U_FAILURE(status)) {
michael@0 494 return;
michael@0 495 }
michael@0 496 const char* numberingSystemName = ns->getName();
michael@0 497 UResourceBundle* rb = ures_open(NULL, inLocale.getName(), &status);
michael@0 498 rb = ures_getByKeyWithFallback(rb, gNumberElementsTag, rb, &status);
michael@0 499 if (U_FAILURE(status)) {
michael@0 500 ures_close(rb);
michael@0 501 return;
michael@0 502 }
michael@0 503 UResourceBundle* shortDataFillIn = NULL;
michael@0 504 UResourceBundle* longDataFillIn = NULL;
michael@0 505 UResourceBundle* shortData = NULL;
michael@0 506 UResourceBundle* longData = NULL;
michael@0 507
michael@0 508 if (uprv_strcmp(numberingSystemName, gLatnTag) != 0) {
michael@0 509 LocalUResourceBundlePointer localResource(
michael@0 510 tryGetByKeyWithFallback(rb, numberingSystemName, NULL, NOT_ROOT, status));
michael@0 511 shortData = tryGetDecimalFallback(
michael@0 512 localResource.getAlias(), gPatternsShort, &shortDataFillIn, NOT_ROOT, status);
michael@0 513 longData = tryGetDecimalFallback(
michael@0 514 localResource.getAlias(), gPatternsLong, &longDataFillIn, NOT_ROOT, status);
michael@0 515 }
michael@0 516 if (U_FAILURE(status)) {
michael@0 517 ures_close(shortDataFillIn);
michael@0 518 ures_close(longDataFillIn);
michael@0 519 ures_close(rb);
michael@0 520 return;
michael@0 521 }
michael@0 522
michael@0 523 // If we haven't found UNUM_SHORT look in latn numbering system. We must
michael@0 524 // succeed at finding UNUM_SHORT here.
michael@0 525 if (shortData == NULL) {
michael@0 526 LocalUResourceBundlePointer latnResource(tryGetByKeyWithFallback(rb, gLatnTag, NULL, MUST, status));
michael@0 527 shortData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsShort, &shortDataFillIn, MUST, status);
michael@0 528 if (longData == NULL) {
michael@0 529 longData = tryGetDecimalFallback(latnResource.getAlias(), gPatternsLong, &longDataFillIn, ANY, status);
michael@0 530 if (longData != NULL && isRoot(longData, status) && !isRoot(shortData, status)) {
michael@0 531 longData = NULL;
michael@0 532 }
michael@0 533 }
michael@0 534 }
michael@0 535 initCDFLocaleStyleData(shortData, &result->shortData, status);
michael@0 536 ures_close(shortDataFillIn);
michael@0 537 if (U_FAILURE(status)) {
michael@0 538 ures_close(longDataFillIn);
michael@0 539 ures_close(rb);
michael@0 540 }
michael@0 541
michael@0 542 if (longData == NULL) {
michael@0 543 result->longData.setToBogus();
michael@0 544 } else {
michael@0 545 initCDFLocaleStyleData(longData, &result->longData, status);
michael@0 546 }
michael@0 547 ures_close(longDataFillIn);
michael@0 548 ures_close(rb);
michael@0 549 }
michael@0 550
michael@0 551 /**
michael@0 552 * tryGetDecimalFallback attempts to fetch the "decimalFormat" resource bundle
michael@0 553 * with a particular style. style is either "patternsShort" or "patternsLong."
michael@0 554 * FillIn, flags, and status work in the same way as in tryGetByKeyWithFallback.
michael@0 555 */
michael@0 556 static UResourceBundle* tryGetDecimalFallback(const UResourceBundle* numberSystemResource, const char* style, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) {
michael@0 557 UResourceBundle* first = tryGetByKeyWithFallback(numberSystemResource, style, fillIn, flags, status);
michael@0 558 UResourceBundle* second = tryGetByKeyWithFallback(first, gDecimalFormatTag, fillIn, flags, status);
michael@0 559 if (fillIn == NULL) {
michael@0 560 ures_close(first);
michael@0 561 }
michael@0 562 return second;
michael@0 563 }
michael@0 564
michael@0 565 // tryGetByKeyWithFallback returns a sub-resource bundle that matches given
michael@0 566 // criteria or NULL if none found. rb is the resource bundle that we are
michael@0 567 // searching. If rb == NULL then this function behaves as if no sub-resource
michael@0 568 // is found; path is the key of the sub-resource,
michael@0 569 // (i.e "foo" but not "foo/bar"); If fillIn is NULL, caller must always call
michael@0 570 // ures_close() on returned resource. See below for example when fillIn is
michael@0 571 // not NULL. flags is ANY or NOT_ROOT. Optionally, these values
michael@0 572 // can be ored with MUST. MUST by itself is the same as ANY | MUST.
michael@0 573 // The locale of the returned sub-resource will either match the
michael@0 574 // flags or the returned sub-resouce will be NULL. If MUST is included in
michael@0 575 // flags, and not suitable sub-resource is found then in addition to returning
michael@0 576 // NULL, this function also sets status to U_MISSING_RESOURCE_ERROR. If MUST
michael@0 577 // is not included in flags, then this function just returns NULL if no
michael@0 578 // such sub-resource is found and will never set status to
michael@0 579 // U_MISSING_RESOURCE_ERROR.
michael@0 580 //
michael@0 581 // Example: This code first searches for "foo/bar" sub-resource without falling
michael@0 582 // back to ROOT. Then searches for "baz" sub-resource as last resort.
michael@0 583 //
michael@0 584 // UResourcebundle* fillIn = NULL;
michael@0 585 // UResourceBundle* data = tryGetByKeyWithFallback(rb, "foo", &fillIn, NON_ROOT, status);
michael@0 586 // data = tryGetByKeyWithFallback(data, "bar", &fillIn, NON_ROOT, status);
michael@0 587 // if (!data) {
michael@0 588 // data = tryGetbyKeyWithFallback(rb, "baz", &fillIn, MUST, status);
michael@0 589 // }
michael@0 590 // if (U_FAILURE(status)) {
michael@0 591 // ures_close(fillIn);
michael@0 592 // return;
michael@0 593 // }
michael@0 594 // doStuffWithNonNullSubresource(data);
michael@0 595 //
michael@0 596 // /* Wrong! don't do the following as it can leak memory if fillIn gets set
michael@0 597 // to NULL. */
michael@0 598 // fillIn = tryGetByKeyWithFallback(rb, "wrong", &fillIn, ANY, status);
michael@0 599 //
michael@0 600 // ures_close(fillIn);
michael@0 601 //
michael@0 602 static UResourceBundle* tryGetByKeyWithFallback(const UResourceBundle* rb, const char* path, UResourceBundle** fillIn, FallbackFlags flags, UErrorCode& status) {
michael@0 603 if (U_FAILURE(status)) {
michael@0 604 return NULL;
michael@0 605 }
michael@0 606 UBool must = (flags & MUST);
michael@0 607 if (rb == NULL) {
michael@0 608 if (must) {
michael@0 609 status = U_MISSING_RESOURCE_ERROR;
michael@0 610 }
michael@0 611 return NULL;
michael@0 612 }
michael@0 613 UResourceBundle* result = NULL;
michael@0 614 UResourceBundle* ownedByUs = NULL;
michael@0 615 if (fillIn == NULL) {
michael@0 616 ownedByUs = ures_getByKeyWithFallback(rb, path, NULL, &status);
michael@0 617 result = ownedByUs;
michael@0 618 } else {
michael@0 619 *fillIn = ures_getByKeyWithFallback(rb, path, *fillIn, &status);
michael@0 620 result = *fillIn;
michael@0 621 }
michael@0 622 if (U_FAILURE(status)) {
michael@0 623 ures_close(ownedByUs);
michael@0 624 if (status == U_MISSING_RESOURCE_ERROR && !must) {
michael@0 625 status = U_ZERO_ERROR;
michael@0 626 }
michael@0 627 return NULL;
michael@0 628 }
michael@0 629 flags = (FallbackFlags) (flags & ~MUST);
michael@0 630 switch (flags) {
michael@0 631 case NOT_ROOT:
michael@0 632 {
michael@0 633 UBool bRoot = isRoot(result, status);
michael@0 634 if (bRoot || U_FAILURE(status)) {
michael@0 635 ures_close(ownedByUs);
michael@0 636 if (must && (status == U_ZERO_ERROR)) {
michael@0 637 status = U_MISSING_RESOURCE_ERROR;
michael@0 638 }
michael@0 639 return NULL;
michael@0 640 }
michael@0 641 return result;
michael@0 642 }
michael@0 643 case ANY:
michael@0 644 return result;
michael@0 645 default:
michael@0 646 ures_close(ownedByUs);
michael@0 647 status = U_ILLEGAL_ARGUMENT_ERROR;
michael@0 648 return NULL;
michael@0 649 }
michael@0 650 }
michael@0 651
michael@0 652 static UBool isRoot(const UResourceBundle* rb, UErrorCode& status) {
michael@0 653 const char* actualLocale = ures_getLocaleByType(
michael@0 654 rb, ULOC_ACTUAL_LOCALE, &status);
michael@0 655 if (U_FAILURE(status)) {
michael@0 656 return FALSE;
michael@0 657 }
michael@0 658 return uprv_strcmp(actualLocale, gRoot) == 0;
michael@0 659 }
michael@0 660
michael@0 661
michael@0 662 // initCDFLocaleStyleData loads formatting data for a particular style.
michael@0 663 // decimalFormatBundle is the "decimalFormat" resource bundle in CLDR.
michael@0 664 // Loaded data stored in result.
michael@0 665 static void initCDFLocaleStyleData(const UResourceBundle* decimalFormatBundle, CDFLocaleStyleData* result, UErrorCode& status) {
michael@0 666 if (U_FAILURE(status)) {
michael@0 667 return;
michael@0 668 }
michael@0 669 // Iterate through all the powers of 10.
michael@0 670 int32_t size = ures_getSize(decimalFormatBundle);
michael@0 671 UResourceBundle* power10 = NULL;
michael@0 672 for (int32_t i = 0; i < size; ++i) {
michael@0 673 power10 = ures_getByIndex(decimalFormatBundle, i, power10, &status);
michael@0 674 if (U_FAILURE(status)) {
michael@0 675 ures_close(power10);
michael@0 676 return;
michael@0 677 }
michael@0 678 populatePower10(power10, result, status);
michael@0 679 if (U_FAILURE(status)) {
michael@0 680 ures_close(power10);
michael@0 681 return;
michael@0 682 }
michael@0 683 }
michael@0 684 ures_close(power10);
michael@0 685 fillInMissing(result);
michael@0 686 }
michael@0 687
michael@0 688 // populatePower10 grabs data for a particular power of 10 from CLDR.
michael@0 689 // The loaded data is stored in result.
michael@0 690 static void populatePower10(const UResourceBundle* power10Bundle, CDFLocaleStyleData* result, UErrorCode& status) {
michael@0 691 if (U_FAILURE(status)) {
michael@0 692 return;
michael@0 693 }
michael@0 694 char* endPtr = NULL;
michael@0 695 double power10 = uprv_strtod(ures_getKey(power10Bundle), &endPtr);
michael@0 696 if (*endPtr != 0) {
michael@0 697 status = U_INTERNAL_PROGRAM_ERROR;
michael@0 698 return;
michael@0 699 }
michael@0 700 int32_t log10Value = computeLog10(power10, FALSE);
michael@0 701 // Silently ignore divisors that are too big.
michael@0 702 if (log10Value == MAX_DIGITS) {
michael@0 703 return;
michael@0 704 }
michael@0 705 int32_t size = ures_getSize(power10Bundle);
michael@0 706 int32_t numZeros = 0;
michael@0 707 UBool otherVariantDefined = FALSE;
michael@0 708 UResourceBundle* variantBundle = NULL;
michael@0 709 // Iterate over all the plural variants for the power of 10
michael@0 710 for (int32_t i = 0; i < size; ++i) {
michael@0 711 variantBundle = ures_getByIndex(power10Bundle, i, variantBundle, &status);
michael@0 712 if (U_FAILURE(status)) {
michael@0 713 ures_close(variantBundle);
michael@0 714 return;
michael@0 715 }
michael@0 716 const char* variant = ures_getKey(variantBundle);
michael@0 717 int32_t resLen;
michael@0 718 const UChar* formatStrP = ures_getString(variantBundle, &resLen, &status);
michael@0 719 if (U_FAILURE(status)) {
michael@0 720 ures_close(variantBundle);
michael@0 721 return;
michael@0 722 }
michael@0 723 UnicodeString formatStr(false, formatStrP, resLen);
michael@0 724 if (uprv_strcmp(variant, gOther) == 0) {
michael@0 725 otherVariantDefined = TRUE;
michael@0 726 }
michael@0 727 int32_t nz = populatePrefixSuffix(
michael@0 728 variant, log10Value, formatStr, result->unitsByVariant, status);
michael@0 729 if (U_FAILURE(status)) {
michael@0 730 ures_close(variantBundle);
michael@0 731 return;
michael@0 732 }
michael@0 733 if (nz != numZeros) {
michael@0 734 // We expect all format strings to have the same number of 0's
michael@0 735 // left of the decimal point.
michael@0 736 if (numZeros != 0) {
michael@0 737 status = U_INTERNAL_PROGRAM_ERROR;
michael@0 738 ures_close(variantBundle);
michael@0 739 return;
michael@0 740 }
michael@0 741 numZeros = nz;
michael@0 742 }
michael@0 743 }
michael@0 744 ures_close(variantBundle);
michael@0 745 // We expect to find an OTHER variant for each power of 10.
michael@0 746 if (!otherVariantDefined) {
michael@0 747 status = U_INTERNAL_PROGRAM_ERROR;
michael@0 748 return;
michael@0 749 }
michael@0 750 double divisor = power10;
michael@0 751 for (int32_t i = 1; i < numZeros; ++i) {
michael@0 752 divisor /= 10.0;
michael@0 753 }
michael@0 754 result->divisors[log10Value] = divisor;
michael@0 755 }
michael@0 756
michael@0 757 // populatePrefixSuffix Adds a specific prefix-suffix pair to result for a
michael@0 758 // given variant and log10 value.
michael@0 759 // variant is 'zero', 'one', 'two', 'few', 'many', or 'other'.
michael@0 760 // formatStr is the format string from which the prefix and suffix are
michael@0 761 // extracted. It is usually of form 'Pefix 000 suffix'.
michael@0 762 // populatePrefixSuffix returns the number of 0's found in formatStr
michael@0 763 // before the decimal point.
michael@0 764 // In the special case that formatStr contains only spaces for prefix
michael@0 765 // and suffix, populatePrefixSuffix returns log10Value + 1.
michael@0 766 static int32_t populatePrefixSuffix(
michael@0 767 const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UErrorCode& status) {
michael@0 768 if (U_FAILURE(status)) {
michael@0 769 return 0;
michael@0 770 }
michael@0 771 int32_t firstIdx = formatStr.indexOf(kZero, LENGTHOF(kZero), 0);
michael@0 772 // We must have 0's in format string.
michael@0 773 if (firstIdx == -1) {
michael@0 774 status = U_INTERNAL_PROGRAM_ERROR;
michael@0 775 return 0;
michael@0 776 }
michael@0 777 int32_t lastIdx = formatStr.lastIndexOf(kZero, LENGTHOF(kZero), firstIdx);
michael@0 778 CDFUnit* unit = createCDFUnit(variant, log10Value, result, status);
michael@0 779 if (U_FAILURE(status)) {
michael@0 780 return 0;
michael@0 781 }
michael@0 782 // Everything up to first 0 is the prefix
michael@0 783 unit->prefix = formatStr.tempSubString(0, firstIdx);
michael@0 784 fixQuotes(unit->prefix);
michael@0 785 // Everything beyond the last 0 is the suffix
michael@0 786 unit->suffix = formatStr.tempSubString(lastIdx + 1);
michael@0 787 fixQuotes(unit->suffix);
michael@0 788
michael@0 789 // If there is effectively no prefix or suffix, ignore the actual number of
michael@0 790 // 0's and act as if the number of 0's matches the size of the number.
michael@0 791 if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) {
michael@0 792 return log10Value + 1;
michael@0 793 }
michael@0 794
michael@0 795 // Calculate number of zeros before decimal point
michael@0 796 int32_t idx = firstIdx + 1;
michael@0 797 while (idx <= lastIdx && formatStr.charAt(idx) == u_0) {
michael@0 798 ++idx;
michael@0 799 }
michael@0 800 return (idx - firstIdx);
michael@0 801 }
michael@0 802
michael@0 803 static UBool onlySpaces(UnicodeString u) {
michael@0 804 return u.trim().length() == 0;
michael@0 805 }
michael@0 806
michael@0 807 // fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j.
michael@0 808 // Modifies s in place.
michael@0 809 static void fixQuotes(UnicodeString& s) {
michael@0 810 QuoteState state = OUTSIDE;
michael@0 811 int32_t len = s.length();
michael@0 812 int32_t dest = 0;
michael@0 813 for (int32_t i = 0; i < len; ++i) {
michael@0 814 UChar ch = s.charAt(i);
michael@0 815 if (ch == u_apos) {
michael@0 816 if (state == INSIDE_EMPTY) {
michael@0 817 s.setCharAt(dest, ch);
michael@0 818 ++dest;
michael@0 819 }
michael@0 820 } else {
michael@0 821 s.setCharAt(dest, ch);
michael@0 822 ++dest;
michael@0 823 }
michael@0 824
michael@0 825 // Update state
michael@0 826 switch (state) {
michael@0 827 case OUTSIDE:
michael@0 828 state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE;
michael@0 829 break;
michael@0 830 case INSIDE_EMPTY:
michael@0 831 case INSIDE_FULL:
michael@0 832 state = ch == u_apos ? OUTSIDE : INSIDE_FULL;
michael@0 833 break;
michael@0 834 default:
michael@0 835 break;
michael@0 836 }
michael@0 837 }
michael@0 838 s.truncate(dest);
michael@0 839 }
michael@0 840
michael@0 841 // fillInMissing ensures that the data in result is complete.
michael@0 842 // result data is complete if for each variant in result, there exists
michael@0 843 // a prefix-suffix pair for each log10 value and there also exists
michael@0 844 // a divisor for each log10 value.
michael@0 845 //
michael@0 846 // First this function figures out for which log10 values, the other
michael@0 847 // variant already had data. These are the same log10 values defined
michael@0 848 // in CLDR.
michael@0 849 //
michael@0 850 // For each log10 value not defined in CLDR, it uses the divisor for
michael@0 851 // the last defined log10 value or 1.
michael@0 852 //
michael@0 853 // Then for each variant, it does the following. For each log10
michael@0 854 // value not defined in CLDR, copy the prefix-suffix pair from the
michael@0 855 // previous log10 value. If log10 value is defined in CLDR but is
michael@0 856 // missing from given variant, copy the prefix-suffix pair for that
michael@0 857 // log10 value from the 'other' variant.
michael@0 858 static void fillInMissing(CDFLocaleStyleData* result) {
michael@0 859 const CDFUnit* otherUnits =
michael@0 860 (const CDFUnit*) uhash_get(result->unitsByVariant, gOther);
michael@0 861 UBool definedInCLDR[MAX_DIGITS];
michael@0 862 double lastDivisor = 1.0;
michael@0 863 for (int32_t i = 0; i < MAX_DIGITS; ++i) {
michael@0 864 if (!otherUnits[i].isSet()) {
michael@0 865 result->divisors[i] = lastDivisor;
michael@0 866 definedInCLDR[i] = FALSE;
michael@0 867 } else {
michael@0 868 lastDivisor = result->divisors[i];
michael@0 869 definedInCLDR[i] = TRUE;
michael@0 870 }
michael@0 871 }
michael@0 872 // Iterate over each variant.
michael@0 873 int32_t pos = -1;
michael@0 874 const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos);
michael@0 875 for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) {
michael@0 876 CDFUnit* units = (CDFUnit*) element->value.pointer;
michael@0 877 for (int32_t i = 0; i < MAX_DIGITS; ++i) {
michael@0 878 if (definedInCLDR[i]) {
michael@0 879 if (!units[i].isSet()) {
michael@0 880 units[i] = otherUnits[i];
michael@0 881 }
michael@0 882 } else {
michael@0 883 if (i == 0) {
michael@0 884 units[0].markAsSet();
michael@0 885 } else {
michael@0 886 units[i] = units[i - 1];
michael@0 887 }
michael@0 888 }
michael@0 889 }
michael@0 890 }
michael@0 891 }
michael@0 892
michael@0 893 // computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest
michael@0 894 // value computeLog10 will return MAX_DIGITS -1 even for
michael@0 895 // numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return
michael@0 896 // up to MAX_DIGITS.
michael@0 897 static int32_t computeLog10(double x, UBool inRange) {
michael@0 898 int32_t result = 0;
michael@0 899 int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS;
michael@0 900 while (x >= 10.0) {
michael@0 901 x /= 10.0;
michael@0 902 ++result;
michael@0 903 if (result == max) {
michael@0 904 break;
michael@0 905 }
michael@0 906 }
michael@0 907 return result;
michael@0 908 }
michael@0 909
michael@0 910 // createCDFUnit returns a pointer to the prefix-suffix pair for a given
michael@0 911 // variant and log10 value within table. If no such prefix-suffix pair is
michael@0 912 // stored in table, one is created within table before returning pointer.
michael@0 913 static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) {
michael@0 914 if (U_FAILURE(status)) {
michael@0 915 return NULL;
michael@0 916 }
michael@0 917 CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant);
michael@0 918 if (cdfUnit == NULL) {
michael@0 919 cdfUnit = new CDFUnit[MAX_DIGITS];
michael@0 920 if (cdfUnit == NULL) {
michael@0 921 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 922 return NULL;
michael@0 923 }
michael@0 924 uhash_put(table, uprv_strdup(variant), cdfUnit, &status);
michael@0 925 if (U_FAILURE(status)) {
michael@0 926 return NULL;
michael@0 927 }
michael@0 928 }
michael@0 929 CDFUnit* result = &cdfUnit[log10Value];
michael@0 930 result->markAsSet();
michael@0 931 return result;
michael@0 932 }
michael@0 933
michael@0 934 // getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given
michael@0 935 // variant and log10 value within table. If the given variant doesn't exist, it
michael@0 936 // falls back to the OTHER variant. Therefore, this method will always return
michael@0 937 // some non-NULL value.
michael@0 938 static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) {
michael@0 939 CharString cvariant;
michael@0 940 UErrorCode status = U_ZERO_ERROR;
michael@0 941 const CDFUnit *cdfUnit = NULL;
michael@0 942 cvariant.appendInvariantChars(variant, status);
michael@0 943 if (!U_FAILURE(status)) {
michael@0 944 cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data());
michael@0 945 }
michael@0 946 if (cdfUnit == NULL) {
michael@0 947 cdfUnit = (const CDFUnit*) uhash_get(table, gOther);
michael@0 948 }
michael@0 949 return &cdfUnit[log10Value];
michael@0 950 }
michael@0 951
michael@0 952 U_NAMESPACE_END
michael@0 953 #endif

mercurial