1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 /*
4 ******************************************************************************
5 * Copyright (C) 1997-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ******************************************************************************
8 *
9 * File uresbund.cpp
10 *
11 * Modification History:
12 *
13 * Date Name Description
14 * 04/01/97 aliu Creation.
15 * 06/14/99 stephen Removed functions taking a filename suffix.
16 * 07/20/99 stephen Changed for UResourceBundle typedef'd to void*
17 * 11/09/99 weiv Added ures_getLocale()
18 * March 2000 weiv Total overhaul - using data in DLLs
19 * 06/20/2000 helena OS/400 port changes; mostly typecast.
20 * 06/24/02 weiv Added support for resource sharing
21 ******************************************************************************
22 */
23
24 #include "unicode/ures.h"
25 #include "unicode/ustring.h"
26 #include "unicode/ucnv.h"
27 #include "bytesinkutil.h"
28 #include "charstr.h"
29 #include "uresimp.h"
30 #include "ustr_imp.h"
31 #include "cwchar.h"
32 #include "ucln_cmn.h"
33 #include "cmemory.h"
34 #include "cstring.h"
35 #include "mutex.h"
36 #include "uhash.h"
37 #include "unicode/uenum.h"
38 #include "uenumimp.h"
39 #include "ulocimp.h"
40 #include "umutex.h"
41 #include "putilimp.h"
42 #include "uassert.h"
43 #include "uresdata.h"
44
45 using namespace icu;
46
47 /*
48 Static cache for already opened resource bundles - mostly for keeping fallback info
49 TODO: This cache should probably be removed when the deprecated code is
50 completely removed.
51 */
52 static UHashtable *cache = nullptr;
53 static icu::UInitOnce gCacheInitOnce {};
54
55 static UMutex resbMutex;
56
57 /* INTERNAL: hashes an entry */
hashEntry(const UHashTok parm)58 static int32_t U_CALLCONV hashEntry(const UHashTok parm) {
59 UResourceDataEntry *b = (UResourceDataEntry *)parm.pointer;
60 UHashTok namekey, pathkey;
61 namekey.pointer = b->fName;
62 pathkey.pointer = b->fPath;
63 return uhash_hashChars(namekey)+37u*uhash_hashChars(pathkey);
64 }
65
66 /* INTERNAL: compares two entries */
compareEntries(const UHashTok p1,const UHashTok p2)67 static UBool U_CALLCONV compareEntries(const UHashTok p1, const UHashTok p2) {
68 UResourceDataEntry *b1 = (UResourceDataEntry *)p1.pointer;
69 UResourceDataEntry *b2 = (UResourceDataEntry *)p2.pointer;
70 UHashTok name1, name2, path1, path2;
71 name1.pointer = b1->fName;
72 name2.pointer = b2->fName;
73 path1.pointer = b1->fPath;
74 path2.pointer = b2->fPath;
75 return (UBool)(uhash_compareChars(name1, name2) &&
76 uhash_compareChars(path1, path2));
77 }
78
79
80 /**
81 * Internal function, gets parts of locale name according
82 * to the position of '_' character
83 */
chopLocale(char * name)84 static UBool chopLocale(char *name) {
85 char *i = uprv_strrchr(name, '_');
86
87 if(i != nullptr) {
88 *i = '\0';
89 return true;
90 }
91
92 return false;
93 }
94
hasVariant(const char * localeID)95 static UBool hasVariant(const char* localeID) {
96 UErrorCode err = U_ZERO_ERROR;
97 int32_t variantLength = uloc_getVariant(localeID, nullptr, 0, &err);
98 return variantLength != 0;
99 }
100
101 // This file contains the tables for doing locale fallback, which are generated
102 // by the CLDR-to-ICU process directly from the CLDR data. This file should only
103 // ever be included from here.
104 #define INCLUDED_FROM_URESBUND_CPP
105 #include "localefallback_data.h"
106
performFallbackLookup(const char * key,const char * keyStrs,const char * valueStrs,const int32_t * lookupTable,int32_t lookupTableLength)107 static const char* performFallbackLookup(const char* key,
108 const char* keyStrs,
109 const char* valueStrs,
110 const int32_t* lookupTable,
111 int32_t lookupTableLength) {
112 const int32_t* bottom = lookupTable;
113 const int32_t* top = lookupTable + lookupTableLength;
114
115 while (bottom < top) {
116 // Effectively, divide by 2 and round down to an even index
117 const int32_t* middle = bottom + (((top - bottom) / 4) * 2);
118 const char* entryKey = &(keyStrs[*middle]);
119 int32_t strcmpResult = uprv_strcmp(key, entryKey);
120 if (strcmpResult == 0) {
121 return &(valueStrs[middle[1]]);
122 } else if (strcmpResult < 0) {
123 top = middle;
124 } else {
125 bottom = middle + 2;
126 }
127 }
128 return nullptr;
129 }
130
getDefaultScript(const CharString & language,const CharString & region)131 static CharString getDefaultScript(const CharString& language, const CharString& region) {
132 const char* defaultScript = nullptr;
133 UErrorCode err = U_ZERO_ERROR;
134
135 // the default script will be "Latn" if we don't find the locale ID in the tables
136 CharString result("Latn", err);
137
138 // if we were passed both language and region, make them into a locale ID and look that up in the default
139 // script table
140 if (!region.isEmpty()) {
141 CharString localeID;
142 localeID.append(language, err).append("_", err).append(region, err);
143 if (U_FAILURE(err)) {
144 return result;
145 }
146 defaultScript = performFallbackLookup(localeID.data(), dsLocaleIDChars, scriptCodeChars, defaultScriptTable, UPRV_LENGTHOF(defaultScriptTable));
147 }
148
149 // if we didn't find anything, look up just the language in the default script table
150 if (defaultScript == nullptr) {
151 defaultScript = performFallbackLookup(language.data(), dsLocaleIDChars, scriptCodeChars, defaultScriptTable, UPRV_LENGTHOF(defaultScriptTable));
152 }
153
154 // if either lookup above succeeded, copy the result from "defaultScript" into "result"; otherwise, return "Latn"
155 if (defaultScript != nullptr) {
156 result.clear();
157 result.append(defaultScript, err);
158 }
159 return result;
160 }
161
162 enum UResOpenType {
163 /**
164 * Open a resource bundle for the locale;
165 * if there is not even a base language bundle, then fall back to the default locale;
166 * if there is no bundle for that either, then load the root bundle.
167 *
168 * This is the default bundle loading behavior.
169 */
170 URES_OPEN_LOCALE_DEFAULT_ROOT,
171 // TODO: ICU ticket #11271 "consistent default locale across locale trees"
172 // Add an option to look at the main locale tree for whether to
173 // fall back to root directly (if the locale has main data) or
174 // fall back to the default locale first (if the locale does not even have main data).
175 /**
176 * Open a resource bundle for the locale;
177 * if there is not even a base language bundle, then load the root bundle;
178 * never fall back to the default locale.
179 *
180 * This is used for algorithms that have good pan-Unicode default behavior,
181 * such as case mappings, collation, and segmentation (BreakIterator).
182 */
183 URES_OPEN_LOCALE_ROOT,
184 /**
185 * Open a resource bundle for the exact bundle name as requested;
186 * no fallbacks, do not load parent bundles.
187 *
188 * This is used for supplemental (non-locale) data.
189 */
190 URES_OPEN_DIRECT
191 };
192 typedef enum UResOpenType UResOpenType;
193
194 /**
195 * Internal function, determines the search path for resource bundle files.
196 * Currently, this function is used only by findFirstExisting() to help search for resource bundle files when a bundle for the specified
197 * locale doesn't exist. The code that supports inheritance of resources between existing resource bundle files continues to
198 * use chopLocale() below.
199 * @param name In-out parameter: On input, the locale ID to get a parent locale ID for (this is a locale's base name, without keywords); on output, the
200 * requested parent locale ID.
201 * @param origName The original locale ID the caller of findFirstExisting() requested. This is the same as `name` on the first call to this function,
202 * but as findFirstExisting() ascends the resource bundle's parent tree, this parameter will continue to be the original locale ID requested.
203 */
getParentLocaleID(char * name,const char * origName,UResOpenType openType)204 static bool getParentLocaleID(char *name, const char *origName, UResOpenType openType) {
205 // early out if the locale ID has a variant code or ends with _
206 size_t nameLen = uprv_strlen(name);
207 if (!nameLen || name[nameLen - 1] == '_' || hasVariant(name)) {
208 return chopLocale(name);
209 }
210
211 UErrorCode err = U_ZERO_ERROR;
212 const char* tempNamePtr = name;
213 CharString language = ulocimp_getLanguage(tempNamePtr, &tempNamePtr, err);
214 if (*tempNamePtr == '_') {
215 ++tempNamePtr;
216 }
217 CharString script = ulocimp_getScript(tempNamePtr, &tempNamePtr, err);
218 if (*tempNamePtr == '_') {
219 ++tempNamePtr;
220 }
221 CharString region = ulocimp_getCountry(tempNamePtr, &tempNamePtr, err);
222 CharString workingLocale;
223 if (U_FAILURE(err)) {
224 // hopefully this never happens...
225 return chopLocale(name);
226 }
227
228 // if the open type is URES_OPEN_LOCALE_DEFAULT_ROOT, first look the locale ID up in the parent locale table;
229 // if that table specifies a parent for it, return that (we don't do this for the other open types-- if we're not
230 // falling back through the system default locale, we also want to do straight truncation fallback instead
231 // of looking things up in the parent locale table-- see https://www.unicode.org/reports/tr35/tr35.html#Parent_Locales:
232 // "Collation data, however, is an exception...")
233 if (openType == URES_OPEN_LOCALE_DEFAULT_ROOT) {
234 const char* parentID = performFallbackLookup(name, parentLocaleChars, parentLocaleChars, parentLocaleTable, UPRV_LENGTHOF(parentLocaleTable));
235 if (parentID != nullptr) {
236 uprv_strcpy(name, parentID);
237 return true;
238 }
239 }
240
241 // if it's not in the parent locale table, figure out the fallback script algorithmically
242 // (see CLDR-15265 for an explanation of the algorithm)
243 if (!script.isEmpty() && !region.isEmpty()) {
244 // if "name" has both script and region, is the script the default script?
245 // - if so, remove it and keep the region
246 // - if not, remove the region and keep the script
247 if (getDefaultScript(language, region) == script.toStringPiece()) {
248 workingLocale.append(language, err).append("_", err).append(region, err);
249 } else {
250 workingLocale.append(language, err).append("_", err).append(script, err);
251 }
252 } else if (!region.isEmpty()) {
253 // if "name" has region but not script, did the original locale ID specify a script?
254 // - if yes, replace the region with the script from the original locale ID
255 // - if no, replace the region with the default script for that language and region
256 UErrorCode err = U_ZERO_ERROR;
257 tempNamePtr = origName;
258 CharString origNameLanguage = ulocimp_getLanguage(tempNamePtr, &tempNamePtr, err);
259 if (*tempNamePtr == '_') {
260 ++tempNamePtr;
261 }
262 CharString origNameScript = ulocimp_getScript(origName, nullptr, err);
263 if (!origNameScript.isEmpty()) {
264 workingLocale.append(language, err).append("_", err).append(origNameScript, err);
265 } else {
266 workingLocale.append(language, err).append("_", err).append(getDefaultScript(language, region), err);
267 }
268 } else if (!script.isEmpty()) {
269 // if "name" has script but not region (and our open type if URES_OPEN_LOCALE_DEFAULT_ROOT), is the script
270 // the default script for the language?
271 // - if so, remove it from the locale ID
272 // - if not, return false to continue up the chain
273 // (we don't do this for other open types for the same reason we don't look things up in the parent
274 // locale table for other open types-- see the reference to UTS #35 above)
275 if (openType != URES_OPEN_LOCALE_DEFAULT_ROOT || getDefaultScript(language, CharString()) == script.toStringPiece()) {
276 workingLocale.append(language, err);
277 } else {
278 return false;
279 }
280 } else {
281 // if "name" just contains a language code, return false so the calling code falls back to "root"
282 return false;
283 }
284 if (U_SUCCESS(err) && !workingLocale.isEmpty()) {
285 uprv_strcpy(name, workingLocale.data());
286 return true;
287 } else {
288 return false;
289 }
290 }
291
292 /**
293 * Called to check whether a name without '_' needs to be checked for a parent.
294 * Some code had assumed that locale IDs with '_' could not have a non-root parent.
295 * We may want a better way of doing this.
296 */
mayHaveParent(char * name)297 static UBool mayHaveParent(char *name) {
298 return (name[0] != 0 && uprv_strstr("nb nn",name) != nullptr);
299 }
300
301 /**
302 * Internal function
303 */
entryIncrease(UResourceDataEntry * entry)304 static void entryIncrease(UResourceDataEntry *entry) {
305 Mutex lock(&resbMutex);
306 entry->fCountExisting++;
307 while(entry->fParent != nullptr) {
308 entry = entry->fParent;
309 entry->fCountExisting++;
310 }
311 }
312
313 /**
314 * Internal function. Tries to find a resource in given Resource
315 * Bundle, as well as in its parents
316 */
getFallbackData(const UResourceBundle * resBundle,const char ** resTag,Resource * res,UErrorCode * status)317 static UResourceDataEntry *getFallbackData(
318 const UResourceBundle *resBundle,
319 const char **resTag, Resource *res, UErrorCode *status) {
320 UResourceDataEntry *dataEntry = resBundle->fData;
321 int32_t indexR = -1;
322 int32_t i = 0;
323 *res = RES_BOGUS;
324 if(dataEntry == nullptr) {
325 *status = U_MISSING_RESOURCE_ERROR;
326 return nullptr;
327 }
328 if(dataEntry->fBogus == U_ZERO_ERROR) { /* if this resource is real, */
329 *res = res_getTableItemByKey(&(dataEntry->fData), dataEntry->fData.rootRes, &indexR, resTag); /* try to get data from there */
330 i++;
331 }
332 if(resBundle->fHasFallback) {
333 // Otherwise, we'll look in parents.
334 while(*res == RES_BOGUS && dataEntry->fParent != nullptr) {
335 dataEntry = dataEntry->fParent;
336 if(dataEntry->fBogus == U_ZERO_ERROR) {
337 i++;
338 *res = res_getTableItemByKey(&(dataEntry->fData), dataEntry->fData.rootRes, &indexR, resTag);
339 }
340 }
341 }
342
343 if(*res == RES_BOGUS) {
344 // If the resource is not found, we need to give an error.
345 *status = U_MISSING_RESOURCE_ERROR;
346 return nullptr;
347 }
348 // If the resource is found in parents, we need to adjust the error.
349 if(i>1) {
350 if(uprv_strcmp(dataEntry->fName, uloc_getDefault())==0 || uprv_strcmp(dataEntry->fName, kRootLocaleName)==0) {
351 *status = U_USING_DEFAULT_WARNING;
352 } else {
353 *status = U_USING_FALLBACK_WARNING;
354 }
355 }
356 return dataEntry;
357 }
358
359 static void
free_entry(UResourceDataEntry * entry)360 free_entry(UResourceDataEntry *entry) {
361 UResourceDataEntry *alias;
362 res_unload(&(entry->fData));
363 if(entry->fName != nullptr && entry->fName != entry->fNameBuffer) {
364 uprv_free(entry->fName);
365 }
366 if(entry->fPath != nullptr) {
367 uprv_free(entry->fPath);
368 }
369 if(entry->fPool != nullptr) {
370 --entry->fPool->fCountExisting;
371 }
372 alias = entry->fAlias;
373 if(alias != nullptr) {
374 while(alias->fAlias != nullptr) {
375 alias = alias->fAlias;
376 }
377 --alias->fCountExisting;
378 }
379 uprv_free(entry);
380 }
381
382 /* Works just like ucnv_flushCache() */
ures_flushCache()383 static int32_t ures_flushCache()
384 {
385 UResourceDataEntry *resB;
386 int32_t pos;
387 int32_t rbDeletedNum = 0;
388 const UHashElement *e;
389 UBool deletedMore;
390
391 /*if shared data hasn't even been lazy evaluated yet
392 * return 0
393 */
394 Mutex lock(&resbMutex);
395 if (cache == nullptr) {
396 return 0;
397 }
398
399 do {
400 deletedMore = false;
401 /*creates an enumeration to iterate through every element in the table */
402 pos = UHASH_FIRST;
403 while ((e = uhash_nextElement(cache, &pos)) != nullptr)
404 {
405 resB = (UResourceDataEntry *) e->value.pointer;
406 /* Deletes only if reference counter == 0
407 * Don't worry about the children of this node.
408 * Those will eventually get deleted too, if not already.
409 * Don't worry about the parents of this node.
410 * Those will eventually get deleted too, if not already.
411 */
412 /* 04/05/2002 [weiv] fCountExisting should now be accurate. If it's not zero, that means that */
413 /* some resource bundles are still open somewhere. */
414
415 if (resB->fCountExisting == 0) {
416 rbDeletedNum++;
417 deletedMore = true;
418 uhash_removeElement(cache, e);
419 free_entry(resB);
420 }
421 }
422 /*
423 * Do it again to catch bundles (aliases, pool bundle) whose fCountExisting
424 * got decremented by free_entry().
425 */
426 } while(deletedMore);
427
428 return rbDeletedNum;
429 }
430
431 #ifdef URES_DEBUG
432 #include <stdio.h>
433
ures_dumpCacheContents()434 U_CAPI UBool U_EXPORT2 ures_dumpCacheContents() {
435 UBool cacheNotEmpty = false;
436 int32_t pos = UHASH_FIRST;
437 const UHashElement *e;
438 UResourceDataEntry *resB;
439
440 Mutex lock(&resbMutex);
441 if (cache == nullptr) {
442 fprintf(stderr,"%s:%d: RB Cache is nullptr.\n", __FILE__, __LINE__);
443 return false;
444 }
445
446 while ((e = uhash_nextElement(cache, &pos)) != nullptr) {
447 cacheNotEmpty=true;
448 resB = (UResourceDataEntry *) e->value.pointer;
449 fprintf(stderr,"%s:%d: RB Cache: Entry @0x%p, refcount %d, name %s:%s. Pool 0x%p, alias 0x%p, parent 0x%p\n",
450 __FILE__, __LINE__,
451 (void*)resB, resB->fCountExisting,
452 resB->fName?resB->fName:"nullptr",
453 resB->fPath?resB->fPath:"nullptr",
454 (void*)resB->fPool,
455 (void*)resB->fAlias,
456 (void*)resB->fParent);
457 }
458
459 fprintf(stderr,"%s:%d: RB Cache still contains %d items.\n", __FILE__, __LINE__, uhash_count(cache));
460 return cacheNotEmpty;
461 }
462
463 #endif
464
ures_cleanup()465 static UBool U_CALLCONV ures_cleanup()
466 {
467 if (cache != nullptr) {
468 ures_flushCache();
469 uhash_close(cache);
470 cache = nullptr;
471 }
472 gCacheInitOnce.reset();
473 return true;
474 }
475
476 /** INTERNAL: Initializes the cache for resources */
createCache(UErrorCode & status)477 static void U_CALLCONV createCache(UErrorCode &status) {
478 U_ASSERT(cache == nullptr);
479 cache = uhash_open(hashEntry, compareEntries, nullptr, &status);
480 ucln_common_registerCleanup(UCLN_COMMON_URES, ures_cleanup);
481 }
482
initCache(UErrorCode * status)483 static void initCache(UErrorCode *status) {
484 umtx_initOnce(gCacheInitOnce, &createCache, *status);
485 }
486
487 /** INTERNAL: sets the name (locale) of the resource bundle to given name */
488
setEntryName(UResourceDataEntry * res,const char * name,UErrorCode * status)489 static void setEntryName(UResourceDataEntry *res, const char *name, UErrorCode *status) {
490 int32_t len = (int32_t)uprv_strlen(name);
491 if(res->fName != nullptr && res->fName != res->fNameBuffer) {
492 uprv_free(res->fName);
493 }
494 if (len < (int32_t)sizeof(res->fNameBuffer)) {
495 res->fName = res->fNameBuffer;
496 }
497 else {
498 res->fName = (char *)uprv_malloc(len+1);
499 }
500 if(res->fName == nullptr) {
501 *status = U_MEMORY_ALLOCATION_ERROR;
502 } else {
503 uprv_strcpy(res->fName, name);
504 }
505 }
506
507 static UResourceDataEntry *
508 getPoolEntry(const char *path, UErrorCode *status);
509
510 /**
511 * INTERNAL: Inits and opens an entry from a data DLL.
512 * CAUTION: resbMutex must be locked when calling this function.
513 */
init_entry(const char * localeID,const char * path,UErrorCode * status)514 static UResourceDataEntry *init_entry(const char *localeID, const char *path, UErrorCode *status) {
515 UResourceDataEntry *r = nullptr;
516 UResourceDataEntry find;
517 /*int32_t hashValue;*/
518 const char *name;
519 char aliasName[100] = { 0 };
520 int32_t aliasLen = 0;
521 /*UBool isAlias = false;*/
522 /*UHashTok hashkey; */
523
524 if(U_FAILURE(*status)) {
525 return nullptr;
526 }
527
528 /* here we try to deduce the right locale name */
529 if(localeID == nullptr) { /* if localeID is nullptr, we're trying to open default locale */
530 name = uloc_getDefault();
531 } else if(*localeID == 0) { /* if localeID is "" then we try to open root locale */
532 name = kRootLocaleName;
533 } else { /* otherwise, we'll open what we're given */
534 name = localeID;
535 }
536
537 find.fName = (char *)name;
538 find.fPath = (char *)path;
539
540 /* calculate the hash value of the entry */
541 /*hashkey.pointer = (void *)&find;*/
542 /*hashValue = hashEntry(hashkey);*/
543
544 /* check to see if we already have this entry */
545 r = (UResourceDataEntry *)uhash_get(cache, &find);
546 if(r == nullptr) {
547 /* if the entry is not yet in the hash table, we'll try to construct a new one */
548 r = (UResourceDataEntry *) uprv_malloc(sizeof(UResourceDataEntry));
549 if(r == nullptr) {
550 *status = U_MEMORY_ALLOCATION_ERROR;
551 return nullptr;
552 }
553
554 uprv_memset(r, 0, sizeof(UResourceDataEntry));
555 /*r->fHashKey = hashValue;*/
556
557 setEntryName(r, name, status);
558 if (U_FAILURE(*status)) {
559 uprv_free(r);
560 return nullptr;
561 }
562
563 if(path != nullptr) {
564 r->fPath = (char *)uprv_strdup(path);
565 if(r->fPath == nullptr) {
566 *status = U_MEMORY_ALLOCATION_ERROR;
567 uprv_free(r);
568 return nullptr;
569 }
570 }
571
572 /* this is the actual loading */
573 res_load(&(r->fData), r->fPath, r->fName, status);
574
575 if (U_FAILURE(*status)) {
576 /* if we failed to load due to an out-of-memory error, exit early. */
577 if (*status == U_MEMORY_ALLOCATION_ERROR) {
578 uprv_free(r);
579 return nullptr;
580 }
581 /* we have no such entry in dll, so it will always use fallback */
582 *status = U_USING_FALLBACK_WARNING;
583 r->fBogus = U_USING_FALLBACK_WARNING;
584 } else { /* if we have a regular entry */
585 Resource aliasres;
586 if (r->fData.usesPoolBundle) {
587 r->fPool = getPoolEntry(r->fPath, status);
588 if (U_SUCCESS(*status)) {
589 const int32_t *poolIndexes = r->fPool->fData.pRoot + 1;
590 if(r->fData.pRoot[1 + URES_INDEX_POOL_CHECKSUM] == poolIndexes[URES_INDEX_POOL_CHECKSUM]) {
591 r->fData.poolBundleKeys = (const char *)(poolIndexes + (poolIndexes[URES_INDEX_LENGTH] & 0xff));
592 r->fData.poolBundleStrings = r->fPool->fData.p16BitUnits;
593 } else {
594 r->fBogus = *status = U_INVALID_FORMAT_ERROR;
595 }
596 } else {
597 r->fBogus = *status;
598 }
599 }
600 if (U_SUCCESS(*status)) {
601 /* handle the alias by trying to get out the %%Alias tag.*/
602 /* We'll try to get alias string from the bundle */
603 aliasres = res_getResource(&(r->fData), "%%ALIAS");
604 if (aliasres != RES_BOGUS) {
605 // No tracing: called during initial data loading
606 const char16_t *alias = res_getStringNoTrace(&(r->fData), aliasres, &aliasLen);
607 if(alias != nullptr && aliasLen > 0) { /* if there is actual alias - unload and load new data */
608 u_UCharsToChars(alias, aliasName, aliasLen+1);
609 r->fAlias = init_entry(aliasName, path, status);
610 }
611 }
612 }
613 }
614
615 {
616 UResourceDataEntry *oldR = nullptr;
617 if((oldR = (UResourceDataEntry *)uhash_get(cache, r)) == nullptr) { /* if the data is not cached */
618 /* just insert it in the cache */
619 UErrorCode cacheStatus = U_ZERO_ERROR;
620 uhash_put(cache, (void *)r, r, &cacheStatus);
621 if (U_FAILURE(cacheStatus)) {
622 *status = cacheStatus;
623 free_entry(r);
624 r = nullptr;
625 }
626 } else {
627 /* somebody have already inserted it while we were working, discard newly opened data */
628 /* Also, we could get here IF we opened an alias */
629 free_entry(r);
630 r = oldR;
631 }
632 }
633
634 }
635 if(r != nullptr) {
636 /* return the real bundle */
637 while(r->fAlias != nullptr) {
638 r = r->fAlias;
639 }
640 r->fCountExisting++; /* we increase its reference count */
641 /* if the resource has a warning */
642 /* we don't want to overwrite a status with no error */
643 if(r->fBogus != U_ZERO_ERROR && U_SUCCESS(*status)) {
644 *status = r->fBogus; /* set the returning status */
645 }
646 }
647 return r;
648 }
649
650 static UResourceDataEntry *
getPoolEntry(const char * path,UErrorCode * status)651 getPoolEntry(const char *path, UErrorCode *status) {
652 UResourceDataEntry *poolBundle = init_entry(kPoolBundleName, path, status);
653 if( U_SUCCESS(*status) &&
654 (poolBundle == nullptr || poolBundle->fBogus != U_ZERO_ERROR || !poolBundle->fData.isPoolBundle)
655 ) {
656 *status = U_INVALID_FORMAT_ERROR;
657 }
658 return poolBundle;
659 }
660
661 /* INTERNAL: */
662 /* CAUTION: resbMutex must be locked when calling this function! */
663 static UResourceDataEntry *
findFirstExisting(const char * path,char * name,const char * defaultLocale,UResOpenType openType,UBool * isRoot,UBool * foundParent,UBool * isDefault,UErrorCode * status)664 findFirstExisting(const char* path, char* name, const char* defaultLocale, UResOpenType openType,
665 UBool *isRoot, UBool *foundParent, UBool *isDefault, UErrorCode* status) {
666 UResourceDataEntry *r = nullptr;
667 UBool hasRealData = false;
668 *foundParent = true; /* we're starting with a fresh name */
669 char origName[ULOC_FULLNAME_CAPACITY];
670
671 uprv_strcpy(origName, name);
672 while(*foundParent && !hasRealData) {
673 r = init_entry(name, path, status);
674 /* Null pointer test */
675 if (U_FAILURE(*status)) {
676 return nullptr;
677 }
678 *isDefault = (UBool)(uprv_strncmp(name, defaultLocale, uprv_strlen(name)) == 0);
679 hasRealData = (UBool)(r->fBogus == U_ZERO_ERROR);
680 if(!hasRealData) {
681 /* this entry is not real. We will discard it. */
682 /* However, the parent line for this entry is */
683 /* not to be used - as there might be parent */
684 /* lines in cache from previous openings that */
685 /* are not updated yet. */
686 r->fCountExisting--;
687 /*entryCloseInt(r);*/
688 r = nullptr;
689 *status = U_USING_FALLBACK_WARNING;
690 } else {
691 uprv_strcpy(name, r->fName); /* this is needed for supporting aliases */
692 }
693
694 *isRoot = (UBool)(uprv_strcmp(name, kRootLocaleName) == 0);
695
696 /*Fallback data stuff*/
697 if (!hasRealData) {
698 *foundParent = getParentLocaleID(name, origName, openType);
699 } else {
700 // we've already found a real resource file; what we return to the caller is the parent
701 // locale ID for inheritance, which should come from chopLocale(), not getParentLocaleID()
702 *foundParent = chopLocale(name);
703 }
704 if (*foundParent && *name == '\0') {
705 uprv_strcpy(name, "und");
706 }
707 }
708 return r;
709 }
710
ures_setIsStackObject(UResourceBundle * resB,UBool state)711 static void ures_setIsStackObject( UResourceBundle* resB, UBool state) {
712 if(state) {
713 resB->fMagic1 = 0;
714 resB->fMagic2 = 0;
715 } else {
716 resB->fMagic1 = MAGIC1;
717 resB->fMagic2 = MAGIC2;
718 }
719 }
720
ures_isStackObject(const UResourceBundle * resB)721 static UBool ures_isStackObject(const UResourceBundle* resB) {
722 return((resB->fMagic1 == MAGIC1 && resB->fMagic2 == MAGIC2)?false:true);
723 }
724
725
ures_initStackObject(UResourceBundle * resB)726 U_CFUNC void ures_initStackObject(UResourceBundle* resB) {
727 uprv_memset(resB, 0, sizeof(UResourceBundle));
728 ures_setIsStackObject(resB, true);
729 }
730
731 U_NAMESPACE_BEGIN
732
StackUResourceBundle()733 StackUResourceBundle::StackUResourceBundle() {
734 ures_initStackObject(&bundle);
735 }
736
~StackUResourceBundle()737 StackUResourceBundle::~StackUResourceBundle() {
738 ures_close(&bundle);
739 }
740
741 U_NAMESPACE_END
742
743 static UBool // returns U_SUCCESS(*status)
loadParentsExceptRoot(UResourceDataEntry * & t1,char name[],int32_t nameCapacity,UBool usingUSRData,char usrDataPath[],UErrorCode * status)744 loadParentsExceptRoot(UResourceDataEntry *&t1,
745 char name[], int32_t nameCapacity,
746 UBool usingUSRData, char usrDataPath[], UErrorCode *status) {
747 if (U_FAILURE(*status)) { return false; }
748 UBool checkParent = true;
749 while (checkParent && t1->fParent == nullptr && !t1->fData.noFallback &&
750 res_getResource(&t1->fData,"%%ParentIsRoot") == RES_BOGUS) {
751 Resource parentRes = res_getResource(&t1->fData, "%%Parent");
752 if (parentRes != RES_BOGUS) { // An explicit parent was found.
753 int32_t parentLocaleLen = 0;
754 // No tracing: called during initial data loading
755 const char16_t *parentLocaleName = res_getStringNoTrace(&(t1->fData), parentRes, &parentLocaleLen);
756 if(parentLocaleName != nullptr && 0 < parentLocaleLen && parentLocaleLen < nameCapacity) {
757 u_UCharsToChars(parentLocaleName, name, parentLocaleLen + 1);
758 if (uprv_strcmp(name, kRootLocaleName) == 0) {
759 return true;
760 }
761 }
762 }
763 // Insert regular parents.
764 UErrorCode parentStatus = U_ZERO_ERROR;
765 UResourceDataEntry *t2 = init_entry(name, t1->fPath, &parentStatus);
766 if (U_FAILURE(parentStatus)) {
767 *status = parentStatus;
768 return false;
769 }
770 UResourceDataEntry *u2 = nullptr;
771 UErrorCode usrStatus = U_ZERO_ERROR;
772 if (usingUSRData) { // This code inserts user override data into the inheritance chain.
773 u2 = init_entry(name, usrDataPath, &usrStatus);
774 // If we failed due to out-of-memory, report that to the caller and exit early.
775 if (usrStatus == U_MEMORY_ALLOCATION_ERROR) {
776 *status = usrStatus;
777 return false;
778 }
779 }
780
781 if (usingUSRData && U_SUCCESS(usrStatus) && u2->fBogus == U_ZERO_ERROR) {
782 t1->fParent = u2;
783 u2->fParent = t2;
784 } else {
785 t1->fParent = t2;
786 if (usingUSRData) {
787 // The USR override data wasn't found, set it to be deleted.
788 u2->fCountExisting = 0;
789 }
790 }
791 t1 = t2;
792 checkParent = chopLocale(name) || mayHaveParent(name);
793 }
794 return true;
795 }
796
797 static UBool // returns U_SUCCESS(*status)
insertRootBundle(UResourceDataEntry * & t1,UErrorCode * status)798 insertRootBundle(UResourceDataEntry *&t1, UErrorCode *status) {
799 if (U_FAILURE(*status)) { return false; }
800 UErrorCode parentStatus = U_ZERO_ERROR;
801 UResourceDataEntry *t2 = init_entry(kRootLocaleName, t1->fPath, &parentStatus);
802 if (U_FAILURE(parentStatus)) {
803 *status = parentStatus;
804 return false;
805 }
806 t1->fParent = t2;
807 t1 = t2;
808 return true;
809 }
810
entryOpen(const char * path,const char * localeID,UResOpenType openType,UErrorCode * status)811 static UResourceDataEntry *entryOpen(const char* path, const char* localeID,
812 UResOpenType openType, UErrorCode* status) {
813 U_ASSERT(openType != URES_OPEN_DIRECT);
814 UErrorCode intStatus = U_ZERO_ERROR;
815 UResourceDataEntry *r = nullptr;
816 UResourceDataEntry *t1 = nullptr;
817 UBool isDefault = false;
818 UBool isRoot = false;
819 UBool hasRealData = false;
820 UBool hasChopped = true;
821 UBool usingUSRData = U_USE_USRDATA && ( path == nullptr || uprv_strncmp(path,U_ICUDATA_NAME,8) == 0);
822
823 char name[ULOC_FULLNAME_CAPACITY];
824 char usrDataPath[96];
825
826 initCache(status);
827
828 if(U_FAILURE(*status)) {
829 return nullptr;
830 }
831
832 uprv_strncpy(name, localeID, sizeof(name) - 1);
833 name[sizeof(name) - 1] = 0;
834
835 if ( usingUSRData ) {
836 if ( path == nullptr ) {
837 uprv_strcpy(usrDataPath, U_USRDATA_NAME);
838 } else {
839 uprv_strncpy(usrDataPath, path, sizeof(usrDataPath) - 1);
840 usrDataPath[0] = 'u';
841 usrDataPath[1] = 's';
842 usrDataPath[2] = 'r';
843 usrDataPath[sizeof(usrDataPath) - 1] = 0;
844 }
845 }
846
847 // Note: We need to query the default locale *before* locking resbMutex.
848 const char *defaultLocale = uloc_getDefault();
849
850 Mutex lock(&resbMutex); // Lock resbMutex until the end of this function.
851
852 /* We're going to skip all the locales that do not have any data */
853 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
854
855 // If we failed due to out-of-memory, report the failure and exit early.
856 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
857 *status = intStatus;
858 goto finish;
859 }
860
861 if(r != nullptr) { /* if there is one real locale, we can look for parents. */
862 t1 = r;
863 hasRealData = true;
864 if ( usingUSRData ) { /* This code inserts user override data into the inheritance chain */
865 UErrorCode usrStatus = U_ZERO_ERROR;
866 UResourceDataEntry *u1 = init_entry(t1->fName, usrDataPath, &usrStatus);
867 // If we failed due to out-of-memory, report the failure and exit early.
868 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
869 *status = intStatus;
870 goto finish;
871 }
872 if ( u1 != nullptr ) {
873 if(u1->fBogus == U_ZERO_ERROR) {
874 u1->fParent = t1;
875 r = u1;
876 } else {
877 /* the USR override data wasn't found, set it to be deleted */
878 u1->fCountExisting = 0;
879 }
880 }
881 }
882 if ((hasChopped || mayHaveParent(name)) && !isRoot) {
883 if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
884 goto finish;
885 }
886 }
887 }
888
889 /* we could have reached this point without having any real data */
890 /* if that is the case, we need to chain in the default locale */
891 if(r==nullptr && openType == URES_OPEN_LOCALE_DEFAULT_ROOT && !isDefault && !isRoot) {
892 /* insert default locale */
893 uprv_strcpy(name, defaultLocale);
894 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
895 // If we failed due to out-of-memory, report the failure and exit early.
896 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
897 *status = intStatus;
898 goto finish;
899 }
900 intStatus = U_USING_DEFAULT_WARNING;
901 if(r != nullptr) { /* the default locale exists */
902 t1 = r;
903 hasRealData = true;
904 isDefault = true;
905 // TODO: Why not if (usingUSRData) { ... } like in the non-default-locale code path?
906 if ((hasChopped || mayHaveParent(name)) && !isRoot) {
907 if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
908 goto finish;
909 }
910 }
911 }
912 }
913
914 /* we could still have r == nullptr at this point - maybe even default locale is not */
915 /* present */
916 if(r == nullptr) {
917 uprv_strcpy(name, kRootLocaleName);
918 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
919 // If we failed due to out-of-memory, report the failure and exit early.
920 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
921 *status = intStatus;
922 goto finish;
923 }
924 if(r != nullptr) {
925 t1 = r;
926 intStatus = U_USING_DEFAULT_WARNING;
927 hasRealData = true;
928 } else { /* we don't even have the root locale */
929 *status = U_MISSING_RESOURCE_ERROR;
930 goto finish;
931 }
932 } else if(!isRoot && uprv_strcmp(t1->fName, kRootLocaleName) != 0 &&
933 t1->fParent == nullptr && !r->fData.noFallback) {
934 if (!insertRootBundle(t1, status)) {
935 goto finish;
936 }
937 if(!hasRealData) {
938 r->fBogus = U_USING_DEFAULT_WARNING;
939 }
940 }
941
942 // TODO: Does this ever loop?
943 while(r != nullptr && !isRoot && t1->fParent != nullptr) {
944 t1->fParent->fCountExisting++;
945 t1 = t1->fParent;
946 }
947
948 finish:
949 if(U_SUCCESS(*status)) {
950 if(intStatus != U_ZERO_ERROR) {
951 *status = intStatus;
952 }
953 return r;
954 } else {
955 return nullptr;
956 }
957 }
958
959 /**
960 * Version of entryOpen() and findFirstExisting() for ures_openDirect(),
961 * with no fallbacks.
962 * Parent and root locale bundles are loaded if
963 * the requested bundle does not have the "nofallback" flag.
964 */
965 static UResourceDataEntry *
entryOpenDirect(const char * path,const char * localeID,UErrorCode * status)966 entryOpenDirect(const char* path, const char* localeID, UErrorCode* status) {
967 initCache(status);
968 if(U_FAILURE(*status)) {
969 return nullptr;
970 }
971
972 // Note: We need to query the default locale *before* locking resbMutex.
973 // If the localeID is nullptr, then we want to use the default locale.
974 if (localeID == nullptr) {
975 localeID = uloc_getDefault();
976 } else if (*localeID == 0) {
977 // If the localeID is "", then we want to use the root locale.
978 localeID = kRootLocaleName;
979 }
980
981 Mutex lock(&resbMutex);
982
983 // findFirstExisting() without fallbacks.
984 UResourceDataEntry *r = init_entry(localeID, path, status);
985 if(U_SUCCESS(*status)) {
986 if(r->fBogus != U_ZERO_ERROR) {
987 r->fCountExisting--;
988 r = nullptr;
989 }
990 } else {
991 r = nullptr;
992 }
993
994 // Some code depends on the ures_openDirect() bundle to have a parent bundle chain,
995 // unless it is marked with "nofallback".
996 UResourceDataEntry *t1 = r;
997 if(r != nullptr && uprv_strcmp(localeID, kRootLocaleName) != 0 && // not root
998 r->fParent == nullptr && !r->fData.noFallback &&
999 uprv_strlen(localeID) < ULOC_FULLNAME_CAPACITY) {
1000 char name[ULOC_FULLNAME_CAPACITY];
1001 uprv_strcpy(name, localeID);
1002 if(!chopLocale(name) || uprv_strcmp(name, kRootLocaleName) == 0 ||
1003 loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), false, nullptr, status)) {
1004 if(uprv_strcmp(t1->fName, kRootLocaleName) != 0 && t1->fParent == nullptr) {
1005 insertRootBundle(t1, status);
1006 }
1007 }
1008 if(U_FAILURE(*status)) {
1009 r = nullptr;
1010 }
1011 }
1012
1013 if(r != nullptr) {
1014 // TODO: Does this ever loop?
1015 while(t1->fParent != nullptr) {
1016 t1->fParent->fCountExisting++;
1017 t1 = t1->fParent;
1018 }
1019 }
1020 return r;
1021 }
1022
1023 /**
1024 * Functions to create and destroy resource bundles.
1025 * CAUTION: resbMutex must be locked when calling this function.
1026 */
1027 /* INTERNAL: */
entryCloseInt(UResourceDataEntry * resB)1028 static void entryCloseInt(UResourceDataEntry *resB) {
1029 UResourceDataEntry *p = resB;
1030
1031 while(resB != nullptr) {
1032 p = resB->fParent;
1033 resB->fCountExisting--;
1034
1035 /* Entries are left in the cache. TODO: add ures_flushCache() to force a flush
1036 of the cache. */
1037 /*
1038 if(resB->fCountExisting <= 0) {
1039 uhash_remove(cache, resB);
1040 if(resB->fBogus == U_ZERO_ERROR) {
1041 res_unload(&(resB->fData));
1042 }
1043 if(resB->fName != nullptr) {
1044 uprv_free(resB->fName);
1045 }
1046 if(resB->fPath != nullptr) {
1047 uprv_free(resB->fPath);
1048 }
1049 uprv_free(resB);
1050 }
1051 */
1052
1053 resB = p;
1054 }
1055 }
1056
1057 /**
1058 * API: closes a resource bundle and cleans up.
1059 */
1060
entryClose(UResourceDataEntry * resB)1061 static void entryClose(UResourceDataEntry *resB) {
1062 Mutex lock(&resbMutex);
1063 entryCloseInt(resB);
1064 }
1065
1066 /*
1067 U_CFUNC void ures_setResPath(UResourceBundle *resB, const char* toAdd) {
1068 if(resB->fResPath == nullptr) {
1069 resB->fResPath = resB->fResBuf;
1070 *(resB->fResPath) = 0;
1071 }
1072 resB->fResPathLen = uprv_strlen(toAdd);
1073 if(RES_BUFSIZE <= resB->fResPathLen+1) {
1074 if(resB->fResPath == resB->fResBuf) {
1075 resB->fResPath = (char *)uprv_malloc((resB->fResPathLen+1)*sizeof(char));
1076 } else {
1077 resB->fResPath = (char *)uprv_realloc(resB->fResPath, (resB->fResPathLen+1)*sizeof(char));
1078 }
1079 }
1080 uprv_strcpy(resB->fResPath, toAdd);
1081 }
1082 */
ures_appendResPath(UResourceBundle * resB,const char * toAdd,int32_t lenToAdd,UErrorCode * status)1083 static void ures_appendResPath(UResourceBundle *resB, const char* toAdd, int32_t lenToAdd, UErrorCode *status) {
1084 int32_t resPathLenOrig = resB->fResPathLen;
1085 if(resB->fResPath == nullptr) {
1086 resB->fResPath = resB->fResBuf;
1087 *(resB->fResPath) = 0;
1088 resB->fResPathLen = 0;
1089 }
1090 resB->fResPathLen += lenToAdd;
1091 if(RES_BUFSIZE <= resB->fResPathLen+1) {
1092 if(resB->fResPath == resB->fResBuf) {
1093 resB->fResPath = (char *)uprv_malloc((resB->fResPathLen+1)*sizeof(char));
1094 /* Check that memory was allocated correctly. */
1095 if (resB->fResPath == nullptr) {
1096 *status = U_MEMORY_ALLOCATION_ERROR;
1097 return;
1098 }
1099 uprv_strcpy(resB->fResPath, resB->fResBuf);
1100 } else {
1101 char *temp = (char *)uprv_realloc(resB->fResPath, (resB->fResPathLen+1)*sizeof(char));
1102 /* Check that memory was reallocated correctly. */
1103 if (temp == nullptr) {
1104 *status = U_MEMORY_ALLOCATION_ERROR;
1105 return;
1106 }
1107 resB->fResPath = temp;
1108 }
1109 }
1110 uprv_strcpy(resB->fResPath + resPathLenOrig, toAdd);
1111 }
1112
ures_freeResPath(UResourceBundle * resB)1113 static void ures_freeResPath(UResourceBundle *resB) {
1114 if (resB->fResPath && resB->fResPath != resB->fResBuf) {
1115 uprv_free(resB->fResPath);
1116 }
1117 resB->fResPath = nullptr;
1118 resB->fResPathLen = 0;
1119 }
1120
1121 static void
ures_closeBundle(UResourceBundle * resB,UBool freeBundleObj)1122 ures_closeBundle(UResourceBundle* resB, UBool freeBundleObj)
1123 {
1124 if(resB != nullptr) {
1125 if(resB->fData != nullptr) {
1126 entryClose(resB->fData);
1127 }
1128 if(resB->fVersion != nullptr) {
1129 uprv_free(resB->fVersion);
1130 }
1131 ures_freeResPath(resB);
1132
1133 if(ures_isStackObject(resB) == false && freeBundleObj) {
1134 uprv_free(resB);
1135 }
1136 #if 0 /*U_DEBUG*/
1137 else {
1138 /* poison the data */
1139 uprv_memset(resB, -1, sizeof(UResourceBundle));
1140 }
1141 #endif
1142 }
1143 }
1144
1145 U_CAPI void U_EXPORT2
ures_close(UResourceBundle * resB)1146 ures_close(UResourceBundle* resB)
1147 {
1148 ures_closeBundle(resB, true);
1149 }
1150
1151 namespace {
1152
1153 UResourceBundle *init_resb_result(
1154 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1155 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1156 int32_t recursionDepth,
1157 UResourceBundle *resB, UErrorCode *status);
1158
1159 // TODO: Try to refactor further, so that we output a dataEntry + Resource + (optionally) resPath,
1160 // rather than a UResourceBundle.
1161 // May need to entryIncrease() the resulting dataEntry.
getAliasTargetAsResourceBundle(const ResourceData & resData,Resource r,const char * key,int32_t idx,UResourceDataEntry * validLocaleDataEntry,const char * containerResPath,int32_t recursionDepth,UResourceBundle * resB,UErrorCode * status)1162 UResourceBundle *getAliasTargetAsResourceBundle(
1163 const ResourceData &resData, Resource r, const char *key, int32_t idx,
1164 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1165 int32_t recursionDepth,
1166 UResourceBundle *resB, UErrorCode *status) {
1167 // TODO: When an error occurs: Should we return nullptr vs. resB?
1168 if (U_FAILURE(*status)) { return resB; }
1169 U_ASSERT(RES_GET_TYPE(r) == URES_ALIAS);
1170 int32_t len = 0;
1171 const char16_t *alias = res_getAlias(&resData, r, &len);
1172 if(len <= 0) {
1173 // bad alias
1174 *status = U_ILLEGAL_ARGUMENT_ERROR;
1175 return resB;
1176 }
1177
1178 // Copy the UTF-16 alias string into an invariant-character string.
1179 //
1180 // We do this so that res_findResource() can modify the path,
1181 // which allows us to remove redundant _res_findResource() variants
1182 // in uresdata.c.
1183 // res_findResource() now NUL-terminates each segment so that table keys
1184 // can always be compared with strcmp() instead of strncmp().
1185 // Saves code there and simplifies testing and code coverage.
1186 //
1187 // markus 2003oct17
1188 CharString chAlias;
1189 chAlias.appendInvariantChars(alias, len, *status);
1190 if (U_FAILURE(*status)) {
1191 return nullptr;
1192 }
1193
1194 // We have an alias, now let's cut it up.
1195 const char *path = nullptr, *locale = nullptr, *keyPath = nullptr;
1196 if(chAlias[0] == RES_PATH_SEPARATOR) {
1197 // There is a path included.
1198 char *chAliasData = chAlias.data();
1199 char *sep = chAliasData + 1;
1200 path = sep;
1201 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1202 if(sep != nullptr) {
1203 *sep++ = 0;
1204 }
1205 if(uprv_strcmp(path, "LOCALE") == 0) {
1206 // This is an XPath alias, starting with "/LOCALE/".
1207 // It contains the path to a resource which should be looked up
1208 // starting in the valid locale.
1209 // TODO: Can/should we forbid a /LOCALE alias without key path?
1210 // It seems weird to alias to the same path, just starting from the valid locale.
1211 // That will often yield an infinite loop.
1212 keyPath = sep;
1213 // Read from the valid locale which we already have.
1214 path = locale = nullptr;
1215 } else {
1216 if(uprv_strcmp(path, "ICUDATA") == 0) { /* want ICU data */
1217 path = nullptr;
1218 }
1219 if (sep == nullptr) {
1220 // TODO: This ends up using the root bundle. Can/should we forbid this?
1221 locale = "";
1222 } else {
1223 locale = sep;
1224 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1225 if(sep != nullptr) {
1226 *sep++ = 0;
1227 }
1228 keyPath = sep;
1229 }
1230 }
1231 } else {
1232 // No path, start with a locale.
1233 char *sep = chAlias.data();
1234 locale = sep;
1235 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1236 if(sep != nullptr) {
1237 *sep++ = 0;
1238 }
1239 keyPath = sep;
1240 path = validLocaleDataEntry->fPath;
1241 }
1242
1243 // Got almost everything, let's try to open.
1244 // First, open the bundle with real data.
1245 LocalUResourceBundlePointer mainRes;
1246 UResourceDataEntry *dataEntry;
1247 if (locale == nullptr) {
1248 // alias = /LOCALE/keyPath
1249 // Read from the valid locale which we already have.
1250 dataEntry = validLocaleDataEntry;
1251 } else {
1252 UErrorCode intStatus = U_ZERO_ERROR;
1253 // TODO: Shouldn't we use ures_open() for locale data bundles (!noFallback)?
1254 mainRes.adoptInstead(ures_openDirect(path, locale, &intStatus));
1255 if(U_FAILURE(intStatus)) {
1256 // We failed to open the resource bundle we're aliasing to.
1257 *status = intStatus;
1258 return resB;
1259 }
1260 dataEntry = mainRes->fData;
1261 }
1262
1263 const char* temp = nullptr;
1264 if(keyPath == nullptr) {
1265 // No key path. This means that we are going to to use the corresponding resource from
1266 // another bundle.
1267 // TODO: Why the special code path?
1268 // Why not put together a key path from containerResPath + key or idx,
1269 // as a comment below suggests, and go into the regular code branch?
1270 // First, we are going to get a corresponding container
1271 // resource to the one we are searching.
1272 r = dataEntry->fData.rootRes;
1273 if(containerResPath) {
1274 chAlias.clear().append(containerResPath, *status);
1275 if (U_FAILURE(*status)) {
1276 return nullptr;
1277 }
1278 char *aKey = chAlias.data();
1279 // TODO: should res_findResource() return a new dataEntry, too?
1280 r = res_findResource(&dataEntry->fData, r, &aKey, &temp);
1281 }
1282 if(key) {
1283 // We need to make keyPath from the containerResPath and
1284 // current key, if there is a key associated.
1285 chAlias.clear().append(key, *status);
1286 if (U_FAILURE(*status)) {
1287 return nullptr;
1288 }
1289 char *aKey = chAlias.data();
1290 r = res_findResource(&dataEntry->fData, r, &aKey, &temp);
1291 } else if(idx != -1) {
1292 // If there is no key, but there is an index, try to get by the index.
1293 // Here we have either a table or an array, so get the element.
1294 int32_t type = RES_GET_TYPE(r);
1295 if(URES_IS_TABLE(type)) {
1296 const char *aKey;
1297 r = res_getTableItemByIndex(&dataEntry->fData, r, idx, &aKey);
1298 } else { /* array */
1299 r = res_getArrayItem(&dataEntry->fData, r, idx);
1300 }
1301 }
1302 if(r != RES_BOGUS) {
1303 resB = init_resb_result(
1304 dataEntry, r, temp, -1, validLocaleDataEntry, nullptr, recursionDepth+1,
1305 resB, status);
1306 } else {
1307 *status = U_MISSING_RESOURCE_ERROR;
1308 }
1309 } else {
1310 // This one is a bit trickier.
1311 // We start finding keys, but after we resolve one alias, the path might continue.
1312 // Consider:
1313 // aliastest:alias { "testtypes/anotheralias/Sequence" }
1314 // anotheralias:alias { "/ICUDATA/sh/CollationElements" }
1315 // aliastest resource should finally have the sequence, not collation elements.
1316 CharString pathBuf(keyPath, *status);
1317 if (U_FAILURE(*status)) {
1318 return nullptr;
1319 }
1320 char *myPath = pathBuf.data();
1321 containerResPath = nullptr;
1322 // Now we have fallback following here.
1323 for(;;) {
1324 r = dataEntry->fData.rootRes;
1325 // TODO: Move containerResPath = nullptr to here,
1326 // consistent with restarting from the rootRes of another bundle?!
1327
1328 // This loop handles 'found' resources over several levels.
1329 while(*myPath && U_SUCCESS(*status)) {
1330 r = res_findResource(&(dataEntry->fData), r, &myPath, &temp);
1331 if(r == RES_BOGUS) {
1332 // No resource found, we don't really want to look anymore on this level.
1333 break;
1334 }
1335 // Found a resource, but it might be an indirection.
1336 resB = init_resb_result(
1337 dataEntry, r, temp, -1,
1338 validLocaleDataEntry, containerResPath, recursionDepth+1,
1339 resB, status);
1340 if (U_FAILURE(*status)) {
1341 break;
1342 }
1343 if (temp == nullptr || uprv_strcmp(keyPath, temp) != 0) {
1344 // The call to init_resb_result() above will set resB->fKeyPath to be
1345 // the same as resB->fKey,
1346 // throwing away any additional path elements if we had them --
1347 // if the key path wasn't just a single resource ID, clear out
1348 // the bundle's key path and re-set it to be equal to keyPath.
1349 ures_freeResPath(resB);
1350 ures_appendResPath(resB, keyPath, (int32_t)uprv_strlen(keyPath), status);
1351 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1352 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1353 }
1354 if (U_FAILURE(*status)) {
1355 break;
1356 }
1357 }
1358 r = resB->fRes; /* switch to a new resource, possibly a new tree */
1359 dataEntry = resB->fData;
1360 containerResPath = resB->fResPath;
1361 }
1362 if (U_FAILURE(*status) || r != RES_BOGUS) {
1363 break;
1364 }
1365 // Fall back to the parent bundle, if there is one.
1366 dataEntry = dataEntry->fParent;
1367 if (dataEntry == nullptr) {
1368 *status = U_MISSING_RESOURCE_ERROR;
1369 break;
1370 }
1371 // Copy the same keyPath again.
1372 myPath = pathBuf.data();
1373 uprv_strcpy(myPath, keyPath);
1374 }
1375 }
1376 if(mainRes.getAlias() == resB) {
1377 mainRes.orphan();
1378 }
1379 ResourceTracer(resB).maybeTrace("getalias");
1380 return resB;
1381 }
1382
1383 // Recursive function, should be called only by itself, by its simpler wrapper,
1384 // or by getAliasTargetAsResourceBundle().
init_resb_result(UResourceDataEntry * dataEntry,Resource r,const char * key,int32_t idx,UResourceDataEntry * validLocaleDataEntry,const char * containerResPath,int32_t recursionDepth,UResourceBundle * resB,UErrorCode * status)1385 UResourceBundle *init_resb_result(
1386 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1387 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1388 int32_t recursionDepth,
1389 UResourceBundle *resB, UErrorCode *status) {
1390 // TODO: When an error occurs: Should we return nullptr vs. resB?
1391 if(status == nullptr || U_FAILURE(*status)) {
1392 return resB;
1393 }
1394 if (validLocaleDataEntry == nullptr) {
1395 *status = U_ILLEGAL_ARGUMENT_ERROR;
1396 return nullptr;
1397 }
1398 if(RES_GET_TYPE(r) == URES_ALIAS) {
1399 // This is an alias, need to exchange with real data.
1400 if(recursionDepth >= URES_MAX_ALIAS_LEVEL) {
1401 *status = U_TOO_MANY_ALIASES_ERROR;
1402 return resB;
1403 }
1404 return getAliasTargetAsResourceBundle(
1405 dataEntry->fData, r, key, idx,
1406 validLocaleDataEntry, containerResPath, recursionDepth, resB, status);
1407 }
1408 if(resB == nullptr) {
1409 resB = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
1410 if (resB == nullptr) {
1411 *status = U_MEMORY_ALLOCATION_ERROR;
1412 return nullptr;
1413 }
1414 ures_setIsStackObject(resB, false);
1415 resB->fResPath = nullptr;
1416 resB->fResPathLen = 0;
1417 } else {
1418 if(resB->fData != nullptr) {
1419 entryClose(resB->fData);
1420 }
1421 if(resB->fVersion != nullptr) {
1422 uprv_free(resB->fVersion);
1423 }
1424 /*
1425 weiv: if stack object was passed in, it doesn't really need to be reinited,
1426 since the purpose of initing is to remove stack junk. However, at this point
1427 we would not do anything to an allocated object, so stack object should be
1428 treated the same
1429 */
1430 /*
1431 if(ures_isStackObject(resB) != false) {
1432 ures_initStackObject(resB);
1433 }
1434 */
1435 if(containerResPath != resB->fResPath) {
1436 ures_freeResPath(resB);
1437 }
1438 }
1439 resB->fData = dataEntry;
1440 entryIncrease(resB->fData);
1441 resB->fHasFallback = false;
1442 resB->fIsTopLevel = false;
1443 resB->fIndex = -1;
1444 resB->fKey = key;
1445 resB->fValidLocaleDataEntry = validLocaleDataEntry;
1446 if(containerResPath != resB->fResPath) {
1447 ures_appendResPath(
1448 resB, containerResPath, static_cast<int32_t>(uprv_strlen(containerResPath)), status);
1449 }
1450 if(key != nullptr) {
1451 ures_appendResPath(resB, key, (int32_t)uprv_strlen(key), status);
1452 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1453 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1454 }
1455 } else if(idx >= 0) {
1456 char buf[256];
1457 int32_t len = T_CString_integerToString(buf, idx, 10);
1458 ures_appendResPath(resB, buf, len, status);
1459 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1460 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1461 }
1462 }
1463 /* Make sure that Purify doesn't complain about uninitialized memory copies. */
1464 {
1465 int32_t usedLen = ((resB->fResBuf == resB->fResPath) ? resB->fResPathLen : 0);
1466 uprv_memset(resB->fResBuf + usedLen, 0, sizeof(resB->fResBuf) - usedLen);
1467 }
1468
1469 resB->fVersion = nullptr;
1470 resB->fRes = r;
1471 resB->fSize = res_countArrayItems(&resB->getResData(), resB->fRes);
1472 ResourceTracer(resB).trace("get");
1473 return resB;
1474 }
1475
init_resb_result(UResourceDataEntry * dataEntry,Resource r,const char * key,int32_t idx,const UResourceBundle * container,UResourceBundle * resB,UErrorCode * status)1476 UResourceBundle *init_resb_result(
1477 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1478 // validLocaleDataEntry + containerResPath
1479 const UResourceBundle *container,
1480 UResourceBundle *resB, UErrorCode *status) {
1481 return init_resb_result(
1482 dataEntry, r, key, idx,
1483 container->fValidLocaleDataEntry, container->fResPath, 0, resB, status);
1484 }
1485
1486 } // namespace
1487
ures_copyResb(UResourceBundle * r,const UResourceBundle * original,UErrorCode * status)1488 UResourceBundle *ures_copyResb(UResourceBundle *r, const UResourceBundle *original, UErrorCode *status) {
1489 UBool isStackObject;
1490 if(U_FAILURE(*status) || r == original) {
1491 return r;
1492 }
1493 if(original != nullptr) {
1494 if(r == nullptr) {
1495 isStackObject = false;
1496 r = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
1497 /* test for nullptr */
1498 if (r == nullptr) {
1499 *status = U_MEMORY_ALLOCATION_ERROR;
1500 return nullptr;
1501 }
1502 } else {
1503 isStackObject = ures_isStackObject(r);
1504 ures_closeBundle(r, false);
1505 }
1506 uprv_memcpy(r, original, sizeof(UResourceBundle));
1507 r->fResPath = nullptr;
1508 r->fResPathLen = 0;
1509 if(original->fResPath) {
1510 ures_appendResPath(r, original->fResPath, original->fResPathLen, status);
1511 }
1512 ures_setIsStackObject(r, isStackObject);
1513 if(r->fData != nullptr) {
1514 entryIncrease(r->fData);
1515 }
1516 }
1517 return r;
1518 }
1519
1520 /**
1521 * Functions to retrieve data from resource bundles.
1522 */
1523
ures_getString(const UResourceBundle * resB,int32_t * len,UErrorCode * status)1524 U_CAPI const char16_t* U_EXPORT2 ures_getString(const UResourceBundle* resB, int32_t* len, UErrorCode* status) {
1525 const char16_t *s;
1526 if (status==nullptr || U_FAILURE(*status)) {
1527 return nullptr;
1528 }
1529 if(resB == nullptr) {
1530 *status = U_ILLEGAL_ARGUMENT_ERROR;
1531 return nullptr;
1532 }
1533 s = res_getString({resB}, &resB->getResData(), resB->fRes, len);
1534 if (s == nullptr) {
1535 *status = U_RESOURCE_TYPE_MISMATCH;
1536 }
1537 return s;
1538 }
1539
1540 static const char *
ures_toUTF8String(const char16_t * s16,int32_t length16,char * dest,int32_t * pLength,UBool forceCopy,UErrorCode * status)1541 ures_toUTF8String(const char16_t *s16, int32_t length16,
1542 char *dest, int32_t *pLength,
1543 UBool forceCopy,
1544 UErrorCode *status) {
1545 int32_t capacity;
1546
1547 if (U_FAILURE(*status)) {
1548 return nullptr;
1549 }
1550 if (pLength != nullptr) {
1551 capacity = *pLength;
1552 } else {
1553 capacity = 0;
1554 }
1555 if (capacity < 0 || (capacity > 0 && dest == nullptr)) {
1556 *status = U_ILLEGAL_ARGUMENT_ERROR;
1557 return nullptr;
1558 }
1559
1560 if (length16 == 0) {
1561 /* empty string, return as read-only pointer */
1562 if (pLength != nullptr) {
1563 *pLength = 0;
1564 }
1565 if (forceCopy) {
1566 u_terminateChars(dest, capacity, 0, status);
1567 return dest;
1568 } else {
1569 return "";
1570 }
1571 } else {
1572 /* We need to transform the string to the destination buffer. */
1573 if (capacity < length16) {
1574 /* No chance for the string to fit. Pure preflighting. */
1575 return u_strToUTF8(nullptr, 0, pLength, s16, length16, status);
1576 }
1577 if (!forceCopy && (length16 <= 0x2aaaaaaa)) {
1578 /*
1579 * We know the string will fit into dest because each char16_t turns
1580 * into at most three UTF-8 bytes. Fill the latter part of dest
1581 * so that callers do not expect to use dest as a string pointer,
1582 * hopefully leading to more robust code for when resource bundles
1583 * may store UTF-8 natively.
1584 * (In which case dest would not be used at all.)
1585 *
1586 * We do not do this if forceCopy=true because then the caller
1587 * expects the string to start exactly at dest.
1588 *
1589 * The test above for <= 0x2aaaaaaa prevents overflows.
1590 * The +1 is for the NUL terminator.
1591 */
1592 int32_t maxLength = 3 * length16 + 1;
1593 if (capacity > maxLength) {
1594 dest += capacity - maxLength;
1595 capacity = maxLength;
1596 }
1597 }
1598 return u_strToUTF8(dest, capacity, pLength, s16, length16, status);
1599 }
1600 }
1601
1602 U_CAPI const char * U_EXPORT2
ures_getUTF8String(const UResourceBundle * resB,char * dest,int32_t * pLength,UBool forceCopy,UErrorCode * status)1603 ures_getUTF8String(const UResourceBundle *resB,
1604 char *dest, int32_t *pLength,
1605 UBool forceCopy,
1606 UErrorCode *status) {
1607 int32_t length16;
1608 const char16_t *s16 = ures_getString(resB, &length16, status);
1609 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
1610 }
1611
ures_getBinary(const UResourceBundle * resB,int32_t * len,UErrorCode * status)1612 U_CAPI const uint8_t* U_EXPORT2 ures_getBinary(const UResourceBundle* resB, int32_t* len,
1613 UErrorCode* status) {
1614 const uint8_t *p;
1615 if (status==nullptr || U_FAILURE(*status)) {
1616 return nullptr;
1617 }
1618 if(resB == nullptr) {
1619 *status = U_ILLEGAL_ARGUMENT_ERROR;
1620 return nullptr;
1621 }
1622 p = res_getBinary({resB}, &resB->getResData(), resB->fRes, len);
1623 if (p == nullptr) {
1624 *status = U_RESOURCE_TYPE_MISMATCH;
1625 }
1626 return p;
1627 }
1628
ures_getIntVector(const UResourceBundle * resB,int32_t * len,UErrorCode * status)1629 U_CAPI const int32_t* U_EXPORT2 ures_getIntVector(const UResourceBundle* resB, int32_t* len,
1630 UErrorCode* status) {
1631 const int32_t *p;
1632 if (status==nullptr || U_FAILURE(*status)) {
1633 return nullptr;
1634 }
1635 if(resB == nullptr) {
1636 *status = U_ILLEGAL_ARGUMENT_ERROR;
1637 return nullptr;
1638 }
1639 p = res_getIntVector({resB}, &resB->getResData(), resB->fRes, len);
1640 if (p == nullptr) {
1641 *status = U_RESOURCE_TYPE_MISMATCH;
1642 }
1643 return p;
1644 }
1645
1646 /* this function returns a signed integer */
1647 /* it performs sign extension */
ures_getInt(const UResourceBundle * resB,UErrorCode * status)1648 U_CAPI int32_t U_EXPORT2 ures_getInt(const UResourceBundle* resB, UErrorCode *status) {
1649 if (status==nullptr || U_FAILURE(*status)) {
1650 return 0xffffffff;
1651 }
1652 if(resB == nullptr) {
1653 *status = U_ILLEGAL_ARGUMENT_ERROR;
1654 return 0xffffffff;
1655 }
1656 if(RES_GET_TYPE(resB->fRes) != URES_INT) {
1657 *status = U_RESOURCE_TYPE_MISMATCH;
1658 return 0xffffffff;
1659 }
1660 return res_getInt({resB}, resB->fRes);
1661 }
1662
ures_getUInt(const UResourceBundle * resB,UErrorCode * status)1663 U_CAPI uint32_t U_EXPORT2 ures_getUInt(const UResourceBundle* resB, UErrorCode *status) {
1664 if (status==nullptr || U_FAILURE(*status)) {
1665 return 0xffffffff;
1666 }
1667 if(resB == nullptr) {
1668 *status = U_ILLEGAL_ARGUMENT_ERROR;
1669 return 0xffffffff;
1670 }
1671 if(RES_GET_TYPE(resB->fRes) != URES_INT) {
1672 *status = U_RESOURCE_TYPE_MISMATCH;
1673 return 0xffffffff;
1674 }
1675 return res_getUInt({resB}, resB->fRes);
1676 }
1677
ures_getType(const UResourceBundle * resB)1678 U_CAPI UResType U_EXPORT2 ures_getType(const UResourceBundle *resB) {
1679 if(resB == nullptr) {
1680 return URES_NONE;
1681 }
1682 return res_getPublicType(resB->fRes);
1683 }
1684
ures_getKey(const UResourceBundle * resB)1685 U_CAPI const char * U_EXPORT2 ures_getKey(const UResourceBundle *resB) {
1686 //
1687 // TODO: Trace ures_getKey? I guess not usually.
1688 //
1689 // We usually get the key string to decide whether we want the value, or to
1690 // make a key-value pair. Tracing the value should suffice.
1691 //
1692 // However, I believe we have some data (e.g., in res_index) where the key
1693 // strings are the data. Tracing the enclosing table should suffice.
1694 //
1695 if(resB == nullptr) {
1696 return nullptr;
1697 }
1698 return(resB->fKey);
1699 }
1700
ures_getSize(const UResourceBundle * resB)1701 U_CAPI int32_t U_EXPORT2 ures_getSize(const UResourceBundle *resB) {
1702 if(resB == nullptr) {
1703 return 0;
1704 }
1705
1706 return resB->fSize;
1707 }
1708
ures_getStringWithAlias(const UResourceBundle * resB,Resource r,int32_t sIndex,int32_t * len,UErrorCode * status)1709 static const char16_t* ures_getStringWithAlias(const UResourceBundle *resB, Resource r, int32_t sIndex, int32_t *len, UErrorCode *status) {
1710 if(RES_GET_TYPE(r) == URES_ALIAS) {
1711 const char16_t* result = 0;
1712 UResourceBundle *tempRes = ures_getByIndex(resB, sIndex, nullptr, status);
1713 result = ures_getString(tempRes, len, status);
1714 ures_close(tempRes);
1715 return result;
1716 } else {
1717 return res_getString({resB, sIndex}, &resB->getResData(), r, len);
1718 }
1719 }
1720
ures_resetIterator(UResourceBundle * resB)1721 U_CAPI void U_EXPORT2 ures_resetIterator(UResourceBundle *resB){
1722 if(resB == nullptr) {
1723 return;
1724 }
1725 resB->fIndex = -1;
1726 }
1727
ures_hasNext(const UResourceBundle * resB)1728 U_CAPI UBool U_EXPORT2 ures_hasNext(const UResourceBundle *resB) {
1729 if(resB == nullptr) {
1730 return false;
1731 }
1732 return (UBool)(resB->fIndex < resB->fSize-1);
1733 }
1734
ures_getNextString(UResourceBundle * resB,int32_t * len,const char ** key,UErrorCode * status)1735 U_CAPI const char16_t* U_EXPORT2 ures_getNextString(UResourceBundle *resB, int32_t* len, const char ** key, UErrorCode *status) {
1736 Resource r = RES_BOGUS;
1737
1738 if (status==nullptr || U_FAILURE(*status)) {
1739 return nullptr;
1740 }
1741 if(resB == nullptr) {
1742 *status = U_ILLEGAL_ARGUMENT_ERROR;
1743 return nullptr;
1744 }
1745
1746 if(resB->fIndex == resB->fSize-1) {
1747 *status = U_INDEX_OUTOFBOUNDS_ERROR;
1748 } else {
1749 resB->fIndex++;
1750 switch(RES_GET_TYPE(resB->fRes)) {
1751 case URES_STRING:
1752 case URES_STRING_V2:
1753 return res_getString({resB}, &resB->getResData(), resB->fRes, len);
1754 case URES_TABLE:
1755 case URES_TABLE16:
1756 case URES_TABLE32:
1757 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, resB->fIndex, key);
1758 if(r == RES_BOGUS && resB->fHasFallback) {
1759 /* TODO: do the fallback */
1760 }
1761 return ures_getStringWithAlias(resB, r, resB->fIndex, len, status);
1762 case URES_ARRAY:
1763 case URES_ARRAY16:
1764 r = res_getArrayItem(&resB->getResData(), resB->fRes, resB->fIndex);
1765 if(r == RES_BOGUS && resB->fHasFallback) {
1766 /* TODO: do the fallback */
1767 }
1768 return ures_getStringWithAlias(resB, r, resB->fIndex, len, status);
1769 case URES_ALIAS:
1770 return ures_getStringWithAlias(resB, resB->fRes, resB->fIndex, len, status);
1771 case URES_INT:
1772 case URES_BINARY:
1773 case URES_INT_VECTOR:
1774 *status = U_RESOURCE_TYPE_MISMATCH;
1775 U_FALLTHROUGH;
1776 default:
1777 return nullptr;
1778 }
1779 }
1780
1781 return nullptr;
1782 }
1783
ures_getNextResource(UResourceBundle * resB,UResourceBundle * fillIn,UErrorCode * status)1784 U_CAPI UResourceBundle* U_EXPORT2 ures_getNextResource(UResourceBundle *resB, UResourceBundle *fillIn, UErrorCode *status) {
1785 const char *key = nullptr;
1786 Resource r = RES_BOGUS;
1787
1788 if (status==nullptr || U_FAILURE(*status)) {
1789 /*return nullptr;*/
1790 return fillIn;
1791 }
1792 if(resB == nullptr) {
1793 *status = U_ILLEGAL_ARGUMENT_ERROR;
1794 /*return nullptr;*/
1795 return fillIn;
1796 }
1797
1798 if(resB->fIndex == resB->fSize-1) {
1799 *status = U_INDEX_OUTOFBOUNDS_ERROR;
1800 /*return nullptr;*/
1801 } else {
1802 resB->fIndex++;
1803 switch(RES_GET_TYPE(resB->fRes)) {
1804 case URES_INT:
1805 case URES_BINARY:
1806 case URES_STRING:
1807 case URES_STRING_V2:
1808 case URES_INT_VECTOR:
1809 return ures_copyResb(fillIn, resB, status);
1810 case URES_TABLE:
1811 case URES_TABLE16:
1812 case URES_TABLE32:
1813 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, resB->fIndex, &key);
1814 if(r == RES_BOGUS && resB->fHasFallback) {
1815 /* TODO: do the fallback */
1816 }
1817 return init_resb_result(resB->fData, r, key, resB->fIndex, resB, fillIn, status);
1818 case URES_ARRAY:
1819 case URES_ARRAY16:
1820 r = res_getArrayItem(&resB->getResData(), resB->fRes, resB->fIndex);
1821 if(r == RES_BOGUS && resB->fHasFallback) {
1822 /* TODO: do the fallback */
1823 }
1824 return init_resb_result(resB->fData, r, key, resB->fIndex, resB, fillIn, status);
1825 default:
1826 /*return nullptr;*/
1827 return fillIn;
1828 }
1829 }
1830 /*return nullptr;*/
1831 return fillIn;
1832 }
1833
ures_getByIndex(const UResourceBundle * resB,int32_t indexR,UResourceBundle * fillIn,UErrorCode * status)1834 U_CAPI UResourceBundle* U_EXPORT2 ures_getByIndex(const UResourceBundle *resB, int32_t indexR, UResourceBundle *fillIn, UErrorCode *status) {
1835 const char* key = nullptr;
1836 Resource r = RES_BOGUS;
1837
1838 if (status==nullptr || U_FAILURE(*status)) {
1839 /*return nullptr;*/
1840 return fillIn;
1841 }
1842 if(resB == nullptr) {
1843 *status = U_ILLEGAL_ARGUMENT_ERROR;
1844 /*return nullptr;*/
1845 return fillIn;
1846 }
1847
1848 if(indexR >= 0 && resB->fSize > indexR) {
1849 switch(RES_GET_TYPE(resB->fRes)) {
1850 case URES_INT:
1851 case URES_BINARY:
1852 case URES_STRING:
1853 case URES_STRING_V2:
1854 case URES_INT_VECTOR:
1855 return ures_copyResb(fillIn, resB, status);
1856 case URES_TABLE:
1857 case URES_TABLE16:
1858 case URES_TABLE32:
1859 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, indexR, &key);
1860 if(r == RES_BOGUS && resB->fHasFallback) {
1861 /* TODO: do the fallback */
1862 }
1863 return init_resb_result(resB->fData, r, key, indexR, resB, fillIn, status);
1864 case URES_ARRAY:
1865 case URES_ARRAY16:
1866 r = res_getArrayItem(&resB->getResData(), resB->fRes, indexR);
1867 if(r == RES_BOGUS && resB->fHasFallback) {
1868 /* TODO: do the fallback */
1869 }
1870 return init_resb_result(resB->fData, r, key, indexR, resB, fillIn, status);
1871 default:
1872 /*return nullptr;*/
1873 return fillIn;
1874 }
1875 } else {
1876 *status = U_MISSING_RESOURCE_ERROR;
1877 }
1878 /*return nullptr;*/
1879 return fillIn;
1880 }
1881
ures_getStringByIndex(const UResourceBundle * resB,int32_t indexS,int32_t * len,UErrorCode * status)1882 U_CAPI const char16_t* U_EXPORT2 ures_getStringByIndex(const UResourceBundle *resB, int32_t indexS, int32_t* len, UErrorCode *status) {
1883 const char* key = nullptr;
1884 Resource r = RES_BOGUS;
1885
1886 if (status==nullptr || U_FAILURE(*status)) {
1887 return nullptr;
1888 }
1889 if(resB == nullptr) {
1890 *status = U_ILLEGAL_ARGUMENT_ERROR;
1891 return nullptr;
1892 }
1893
1894 if(indexS >= 0 && resB->fSize > indexS) {
1895 switch(RES_GET_TYPE(resB->fRes)) {
1896 case URES_STRING:
1897 case URES_STRING_V2:
1898 return res_getString({resB}, &resB->getResData(), resB->fRes, len);
1899 case URES_TABLE:
1900 case URES_TABLE16:
1901 case URES_TABLE32:
1902 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, indexS, &key);
1903 if(r == RES_BOGUS && resB->fHasFallback) {
1904 /* TODO: do the fallback */
1905 }
1906 return ures_getStringWithAlias(resB, r, indexS, len, status);
1907 case URES_ARRAY:
1908 case URES_ARRAY16:
1909 r = res_getArrayItem(&resB->getResData(), resB->fRes, indexS);
1910 if(r == RES_BOGUS && resB->fHasFallback) {
1911 /* TODO: do the fallback */
1912 }
1913 return ures_getStringWithAlias(resB, r, indexS, len, status);
1914 case URES_ALIAS:
1915 return ures_getStringWithAlias(resB, resB->fRes, indexS, len, status);
1916 case URES_INT:
1917 case URES_BINARY:
1918 case URES_INT_VECTOR:
1919 *status = U_RESOURCE_TYPE_MISMATCH;
1920 break;
1921 default:
1922 /* must not occur */
1923 *status = U_INTERNAL_PROGRAM_ERROR;
1924 break;
1925 }
1926 } else {
1927 *status = U_MISSING_RESOURCE_ERROR;
1928 }
1929 return nullptr;
1930 }
1931
1932 U_CAPI const char * U_EXPORT2
ures_getUTF8StringByIndex(const UResourceBundle * resB,int32_t idx,char * dest,int32_t * pLength,UBool forceCopy,UErrorCode * status)1933 ures_getUTF8StringByIndex(const UResourceBundle *resB,
1934 int32_t idx,
1935 char *dest, int32_t *pLength,
1936 UBool forceCopy,
1937 UErrorCode *status) {
1938 int32_t length16;
1939 const char16_t *s16 = ures_getStringByIndex(resB, idx, &length16, status);
1940 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
1941 }
1942
1943 /*U_CAPI const char *ures_getResPath(UResourceBundle *resB) {
1944 return resB->fResPath;
1945 }*/
1946
1947 U_CAPI UResourceBundle* U_EXPORT2
ures_findResource(const char * path,UResourceBundle * fillIn,UErrorCode * status)1948 ures_findResource(const char* path, UResourceBundle *fillIn, UErrorCode *status)
1949 {
1950 UResourceBundle *first = nullptr;
1951 UResourceBundle *result = fillIn;
1952 char *packageName = nullptr;
1953 char *pathToResource = nullptr, *save = nullptr;
1954 char *locale = nullptr, *localeEnd = nullptr;
1955 int32_t length;
1956
1957 if(status == nullptr || U_FAILURE(*status)) {
1958 return result;
1959 }
1960
1961 length = (int32_t)(uprv_strlen(path)+1);
1962 save = pathToResource = (char *)uprv_malloc(length*sizeof(char));
1963 /* test for nullptr */
1964 if(pathToResource == nullptr) {
1965 *status = U_MEMORY_ALLOCATION_ERROR;
1966 return result;
1967 }
1968 uprv_memcpy(pathToResource, path, length);
1969
1970 locale = pathToResource;
1971 if(*pathToResource == RES_PATH_SEPARATOR) { /* there is a path specification */
1972 pathToResource++;
1973 packageName = pathToResource;
1974 pathToResource = uprv_strchr(pathToResource, RES_PATH_SEPARATOR);
1975 if(pathToResource == nullptr) {
1976 *status = U_ILLEGAL_ARGUMENT_ERROR;
1977 } else {
1978 *pathToResource = 0;
1979 locale = pathToResource+1;
1980 }
1981 }
1982
1983 localeEnd = uprv_strchr(locale, RES_PATH_SEPARATOR);
1984 if(localeEnd != nullptr) {
1985 *localeEnd = 0;
1986 }
1987
1988 first = ures_open(packageName, locale, status);
1989
1990 if(U_SUCCESS(*status)) {
1991 if(localeEnd) {
1992 result = ures_findSubResource(first, localeEnd+1, fillIn, status);
1993 } else {
1994 result = ures_copyResb(fillIn, first, status);
1995 }
1996 ures_close(first);
1997 }
1998 uprv_free(save);
1999 return result;
2000 }
2001
2002 U_CAPI UResourceBundle* U_EXPORT2
ures_findSubResource(const UResourceBundle * resB,char * path,UResourceBundle * fillIn,UErrorCode * status)2003 ures_findSubResource(const UResourceBundle *resB, char* path, UResourceBundle *fillIn, UErrorCode *status)
2004 {
2005 Resource res = RES_BOGUS;
2006 UResourceBundle *result = fillIn;
2007 const char *key;
2008
2009 if(status == nullptr || U_FAILURE(*status)) {
2010 return result;
2011 }
2012
2013 /* here we do looping and circular alias checking */
2014 /* this loop is here because aliasing is resolved on this level, not on res level */
2015 /* so, when we encounter an alias, it is not an aggregate resource, so we return */
2016 do {
2017 res = res_findResource(&resB->getResData(), resB->fRes, &path, &key);
2018 if(res != RES_BOGUS) {
2019 result = init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
2020 resB = result;
2021 } else {
2022 *status = U_MISSING_RESOURCE_ERROR;
2023 break;
2024 }
2025 } while(*path); /* there is more stuff in the path */
2026
2027 return result;
2028 }
2029 U_CAPI const char16_t* U_EXPORT2
ures_getStringByKeyWithFallback(const UResourceBundle * resB,const char * inKey,int32_t * len,UErrorCode * status)2030 ures_getStringByKeyWithFallback(const UResourceBundle *resB,
2031 const char* inKey,
2032 int32_t* len,
2033 UErrorCode *status) {
2034
2035 UResourceBundle stack;
2036 const char16_t* retVal = nullptr;
2037 ures_initStackObject(&stack);
2038 ures_getByKeyWithFallback(resB, inKey, &stack, status);
2039 int32_t length;
2040 retVal = ures_getString(&stack, &length, status);
2041 ures_close(&stack);
2042 if (U_FAILURE(*status)) {
2043 return nullptr;
2044 }
2045 if (length == 3 && retVal[0] == EMPTY_SET && retVal[1] == EMPTY_SET && retVal[2] == EMPTY_SET ) {
2046 retVal = nullptr;
2047 length = 0;
2048 *status = U_MISSING_RESOURCE_ERROR;
2049 }
2050 if (len != nullptr) {
2051 *len = length;
2052 }
2053 return retVal;
2054 }
2055
2056 /*
2057 Like res_getTableItemByKey but accepts full paths like "NumberElements/latn/patternsShort".
2058 */
getTableItemByKeyPath(const ResourceData * pResData,Resource table,const char * key)2059 static Resource getTableItemByKeyPath(const ResourceData *pResData, Resource table, const char *key) {
2060 Resource resource = table; /* The current resource */
2061 icu::CharString path;
2062 UErrorCode errorCode = U_ZERO_ERROR;
2063 path.append(key, errorCode);
2064 if (U_FAILURE(errorCode)) { return RES_BOGUS; }
2065 char *pathPart = path.data(); /* Path from current resource to desired resource */
2066 UResType type = (UResType)RES_GET_TYPE(resource); /* the current resource type */
2067 while (*pathPart && resource != RES_BOGUS && URES_IS_CONTAINER(type)) {
2068 char *nextPathPart = uprv_strchr(pathPart, RES_PATH_SEPARATOR);
2069 if (nextPathPart != nullptr) {
2070 *nextPathPart = 0; /* Terminating null for this part of path. */
2071 nextPathPart++;
2072 } else {
2073 nextPathPart = uprv_strchr(pathPart, 0);
2074 }
2075 int32_t t;
2076 const char *pathP = pathPart;
2077 resource = res_getTableItemByKey(pResData, resource, &t, &pathP);
2078 type = (UResType)RES_GET_TYPE(resource);
2079 pathPart = nextPathPart;
2080 }
2081 if (*pathPart) {
2082 return RES_BOGUS;
2083 }
2084 return resource;
2085 }
2086
createPath(const char * origResPath,int32_t origResPathLen,const char * resPath,int32_t resPathLen,const char * inKey,CharString & path,UErrorCode * status)2087 static void createPath(const char* origResPath,
2088 int32_t origResPathLen,
2089 const char* resPath,
2090 int32_t resPathLen,
2091 const char* inKey,
2092 CharString& path,
2093 UErrorCode* status) {
2094 // This is a utility function used by ures_getByKeyWithFallback() below. This function builds a path from
2095 // resPath and inKey, returning the result in `path`. Originally, this function just cleared `path` and
2096 // appended resPath and inKey to it, but that caused problems for horizontal inheritance.
2097 //
2098 // In normal cases, resPath is the same as origResPath, but if ures_getByKeyWithFallback() has followed an
2099 // alias, resPath may be different from origResPath. Not only may the existing path elements be different,
2100 // but resPath may also have MORE path elements than origResPath did. If it does, those additional path
2101 // elements SUPERSEDE the corresponding elements of inKey. So this code counts the number of elements in
2102 // resPath and origResPath and, for each path element in resPath that doesn't have a counterpart in origResPath,
2103 // deletes a path element from the beginning of inKey. The remainder of inKey is then appended to
2104 // resPath to form the result. (We're not using uprv_strchr() here because resPath and origResPath may
2105 // not be zero-terminated.)
2106 path.clear();
2107 const char* key = inKey;
2108 if (resPathLen > 0) {
2109 path.append(resPath, resPathLen, *status);
2110 if (U_SUCCESS(*status)) {
2111 const char* resPathLimit = resPath + resPathLen;
2112 const char* origResPathLimit = origResPath + origResPathLen;
2113 const char* resPathPtr = resPath;
2114 const char* origResPathPtr = origResPath;
2115
2116 // Remove from the beginning of resPath the number of segments that are contained in origResPath.
2117 // If origResPath has MORE segments than resPath, this will leave resPath as the empty string.
2118 while (origResPathPtr < origResPathLimit && resPathPtr < resPathLimit) {
2119 while (origResPathPtr < origResPathLimit && *origResPathPtr != RES_PATH_SEPARATOR) {
2120 ++origResPathPtr;
2121 }
2122 if (origResPathPtr < origResPathLimit && *origResPathPtr == RES_PATH_SEPARATOR) {
2123 ++origResPathPtr;
2124 }
2125 while (resPathPtr < resPathLimit && *resPathPtr != RES_PATH_SEPARATOR) {
2126 ++resPathPtr;
2127 }
2128 if (resPathPtr < resPathLimit && *resPathPtr == RES_PATH_SEPARATOR) {
2129 ++resPathPtr;
2130 }
2131 }
2132
2133 // New remove from the beginning of `key` the number of segments remaining in resPath.
2134 // If resPath has more segments than `key` does, `key` will end up empty.
2135 while (resPathPtr < resPathLimit && *key != '\0') {
2136 while (resPathPtr < resPathLimit && *resPathPtr != RES_PATH_SEPARATOR) {
2137 ++resPathPtr;
2138 }
2139 if (resPathPtr < resPathLimit && *resPathPtr == RES_PATH_SEPARATOR) {
2140 ++resPathPtr;
2141 }
2142 while (*key != '\0' && *key != RES_PATH_SEPARATOR) {
2143 ++key;
2144 }
2145 if (*key == RES_PATH_SEPARATOR) {
2146 ++key;
2147 }
2148 }
2149 }
2150 // Finally, append what's left of `key` to `path`. What you end up with here is `resPath`, plus
2151 // any pieces of `key` that aren't superseded by `resPath`.
2152 // Or, to put it another way, calculate <#-segments-in-key> - (<#-segments-in-resPath> - <#-segments-in-origResPath>),
2153 // and append that many segments from the end of `key` to `resPath` to produce the result.
2154 path.append(key, *status);
2155 } else {
2156 path.append(inKey, *status);
2157 }
2158 }
2159
2160 U_CAPI UResourceBundle* U_EXPORT2
ures_getByKeyWithFallback(const UResourceBundle * resB,const char * inKey,UResourceBundle * fillIn,UErrorCode * status)2161 ures_getByKeyWithFallback(const UResourceBundle *resB,
2162 const char* inKey,
2163 UResourceBundle *fillIn,
2164 UErrorCode *status) {
2165 Resource res = RES_BOGUS, rootRes = RES_BOGUS;
2166 UResourceBundle *helper = nullptr;
2167
2168 if (status==nullptr || U_FAILURE(*status)) {
2169 return fillIn;
2170 }
2171 if(resB == nullptr) {
2172 *status = U_ILLEGAL_ARGUMENT_ERROR;
2173 return fillIn;
2174 }
2175
2176 int32_t type = RES_GET_TYPE(resB->fRes);
2177 if(URES_IS_TABLE(type)) {
2178 const char* origResPath = resB->fResPath;
2179 int32_t origResPathLen = resB->fResPathLen;
2180 res = getTableItemByKeyPath(&resB->getResData(), resB->fRes, inKey);
2181 const char* key = inKey;
2182 bool didRootOnce = false;
2183 if(res == RES_BOGUS) {
2184 UResourceDataEntry *dataEntry = resB->fData;
2185 CharString path;
2186 char *myPath = nullptr;
2187 const char* resPath = resB->fResPath;
2188 int32_t len = resB->fResPathLen;
2189 while(res == RES_BOGUS && (dataEntry->fParent != nullptr || !didRootOnce)) { /* Otherwise, we'll look in parents */
2190 if (dataEntry->fParent != nullptr) {
2191 dataEntry = dataEntry->fParent;
2192 } else {
2193 // We can't just stop when we get to a bundle whose fParent is nullptr. That'll work most of the time,
2194 // but if the bundle that the caller passed to us was "root" (which happens in getAllItemsWithFallback(),
2195 // this function will drop right out without doing anything if "root" doesn't contain the exact key path
2196 // specified. In that case, we need one extra time through this loop to make sure we follow any
2197 // applicable aliases at the root level.
2198 didRootOnce = true;
2199 }
2200 rootRes = dataEntry->fData.rootRes;
2201
2202 if(dataEntry->fBogus == U_ZERO_ERROR) {
2203 createPath(origResPath, origResPathLen, resPath, len, inKey, path, status);
2204 if (U_FAILURE(*status)) {
2205 ures_close(helper);
2206 return fillIn;
2207 }
2208 myPath = path.data();
2209 key = inKey;
2210 do {
2211 res = res_findResource(&(dataEntry->fData), rootRes, &myPath, &key);
2212 if (RES_GET_TYPE(res) == URES_ALIAS && *myPath) {
2213 /* We hit an alias, but we didn't finish following the path. */
2214 helper = init_resb_result(dataEntry, res, nullptr, -1, resB, helper, status);
2215 /*helper = init_resb_result(dataEntry, res, inKey, -1, resB, helper, status);*/
2216 if(helper) {
2217 dataEntry = helper->fData;
2218 rootRes = helper->fRes;
2219 resPath = helper->fResPath;
2220 len = helper->fResPathLen;
2221
2222 } else {
2223 break;
2224 }
2225 } else if (res == RES_BOGUS) {
2226 break;
2227 }
2228 } while(*myPath); /* Continue until the whole path is consumed */
2229 }
2230 }
2231 /*dataEntry = getFallbackData(resB, &key, &res, status);*/
2232 if(res != RES_BOGUS) {
2233 /* check if resB->fResPath gives the right name here */
2234 if(uprv_strcmp(dataEntry->fName, uloc_getDefault())==0 || uprv_strcmp(dataEntry->fName, kRootLocaleName)==0) {
2235 *status = U_USING_DEFAULT_WARNING;
2236 } else {
2237 *status = U_USING_FALLBACK_WARNING;
2238 }
2239
2240 fillIn = init_resb_result(dataEntry, res, key, -1, resB, fillIn, status);
2241 if (resPath != nullptr) {
2242 createPath(origResPath, origResPathLen, resPath, len, inKey, path, status);
2243 } else {
2244 const char* separator = nullptr;
2245 if (fillIn->fResPath != nullptr) {
2246 separator = uprv_strchr(fillIn->fResPath, RES_PATH_SEPARATOR);
2247 }
2248 if (separator != nullptr && separator[1] != '\0') {
2249 createPath(origResPath, origResPathLen, fillIn->fResPath,
2250 static_cast<int32_t>(uprv_strlen(fillIn->fResPath)), inKey, path, status);
2251 } else {
2252 createPath(origResPath, origResPathLen, "", 0, inKey, path, status);
2253 }
2254 }
2255 ures_freeResPath(fillIn);
2256 ures_appendResPath(fillIn, path.data(), path.length(), status);
2257 if(fillIn->fResPath[fillIn->fResPathLen-1] != RES_PATH_SEPARATOR) {
2258 ures_appendResPath(fillIn, RES_PATH_SEPARATOR_S, 1, status);
2259 }
2260 } else {
2261 *status = U_MISSING_RESOURCE_ERROR;
2262 }
2263 } else {
2264 fillIn = init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
2265 }
2266 }
2267 else {
2268 *status = U_RESOURCE_TYPE_MISMATCH;
2269 }
2270 ures_close(helper);
2271 return fillIn;
2272 }
2273
2274 namespace {
2275
getAllItemsWithFallback(const UResourceBundle * bundle,ResourceDataValue & value,ResourceSink & sink,UErrorCode & errorCode)2276 void getAllItemsWithFallback(
2277 const UResourceBundle *bundle, ResourceDataValue &value,
2278 ResourceSink &sink, UErrorCode &errorCode) {
2279 if (U_FAILURE(errorCode)) { return; }
2280 // We recursively enumerate child-first,
2281 // only storing parent items in the absence of child items.
2282 // The sink needs to store a placeholder value for the no-fallback/no-inheritance marker
2283 // to prevent a parent item from being stored.
2284 //
2285 // It would be possible to recursively enumerate parent-first,
2286 // overriding parent items with child items.
2287 // When the sink sees the no-fallback/no-inheritance marker,
2288 // then it would remove the parent's item.
2289 // We would deserialize parent values even though they are overridden in a child bundle.
2290 value.setData(bundle->getResData());
2291 value.setValidLocaleDataEntry(bundle->fValidLocaleDataEntry);
2292 UResourceDataEntry *parentEntry = bundle->fData->fParent;
2293 UBool hasParent = parentEntry != nullptr && U_SUCCESS(parentEntry->fBogus);
2294 value.setResource(bundle->fRes, ResourceTracer(bundle));
2295 sink.put(bundle->fKey, value, !hasParent, errorCode);
2296 if (hasParent) {
2297 // We might try to query the sink whether
2298 // any fallback from the parent bundle is still possible.
2299
2300 // Turn the parent UResourceDataEntry into a UResourceBundle,
2301 // much like in ures_openWithType().
2302 // TODO: See if we can refactor ures_getByKeyWithFallback()
2303 // and pull out an inner function that takes and returns a UResourceDataEntry
2304 // so that we need not create UResourceBundle objects.
2305 StackUResourceBundle parentBundle;
2306 UResourceBundle &parentRef = parentBundle.ref();
2307 parentRef.fData = parentEntry;
2308 parentRef.fValidLocaleDataEntry = bundle->fValidLocaleDataEntry;
2309 parentRef.fHasFallback = !parentRef.getResData().noFallback;
2310 parentRef.fIsTopLevel = true;
2311 parentRef.fRes = parentRef.getResData().rootRes;
2312 parentRef.fSize = res_countArrayItems(&parentRef.getResData(), parentRef.fRes);
2313 parentRef.fIndex = -1;
2314 entryIncrease(parentEntry);
2315
2316 // Look up the container item in the parent bundle.
2317 StackUResourceBundle containerBundle;
2318 const UResourceBundle *rb;
2319 UErrorCode pathErrorCode = U_ZERO_ERROR; // Ignore if parents up to root do not have this path.
2320 if (bundle->fResPath == nullptr || *bundle->fResPath == 0) {
2321 rb = parentBundle.getAlias();
2322 } else {
2323 rb = ures_getByKeyWithFallback(parentBundle.getAlias(), bundle->fResPath,
2324 containerBundle.getAlias(), &pathErrorCode);
2325 }
2326 if (U_SUCCESS(pathErrorCode)) {
2327 getAllItemsWithFallback(rb, value, sink, errorCode);
2328 }
2329 }
2330 }
2331
2332 struct GetAllChildrenSink : public ResourceSink {
2333 // Destination sink
2334 ResourceSink& dest;
2335
GetAllChildrenSink__anon5b0bdabb0211::GetAllChildrenSink2336 GetAllChildrenSink(ResourceSink& dest)
2337 : dest(dest) {}
2338 virtual ~GetAllChildrenSink() override;
put__anon5b0bdabb0211::GetAllChildrenSink2339 virtual void put(const char *key, ResourceValue &value, UBool isRoot,
2340 UErrorCode &errorCode) override {
2341 ResourceTable itemsTable = value.getTable(errorCode);
2342 if (U_FAILURE(errorCode)) { return; }
2343 for (int32_t i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
2344 if (value.getType() == URES_ALIAS) {
2345 ResourceDataValue& rdv = static_cast<ResourceDataValue&>(value);
2346 StackUResourceBundle stackTempBundle;
2347 UResourceBundle* aliasRB = getAliasTargetAsResourceBundle(rdv.getData(), rdv.getResource(), nullptr, -1,
2348 rdv.getValidLocaleDataEntry(), nullptr, 0,
2349 stackTempBundle.getAlias(), &errorCode);
2350 if (U_SUCCESS(errorCode)) {
2351 ResourceDataValue aliasedValue;
2352 aliasedValue.setData(aliasRB->getResData());
2353 aliasedValue.setValidLocaleDataEntry(aliasRB->fValidLocaleDataEntry);
2354 aliasedValue.setResource(aliasRB->fRes, ResourceTracer(aliasRB));
2355
2356 if (aliasedValue.getType() != URES_TABLE) {
2357 dest.put(key, aliasedValue, isRoot, errorCode);
2358 } else {
2359 // if the resource we're aliasing over to is a table, the sink might iterate over its contents.
2360 // If it does, it'll get only the things defined in the actual alias target, not the things
2361 // the target inherits from its parent resources. So we walk the parent chain for the *alias target*,
2362 // calling dest.put() for each of the parent tables we could be inheriting from. This means
2363 // that dest.put() has to iterate over the children of multiple tables to get all of the inherited
2364 // resource values, but it already has to do that to handle normal vertical inheritance.
2365 UResType aliasedValueType = URES_TABLE;
2366 CharString tablePath;
2367 tablePath.append(aliasRB->fResPath, errorCode);
2368 const char* parentKey = key; // dest.put() changes the key
2369 dest.put(parentKey, aliasedValue, isRoot, errorCode);
2370 UResourceDataEntry* entry = aliasRB->fData;
2371 Resource res = aliasRB->fRes;
2372 while (aliasedValueType == URES_TABLE && entry->fParent != nullptr) {
2373 CharString localPath;
2374 localPath.copyFrom(tablePath, errorCode);
2375 char* localPathAsCharPtr = localPath.data();
2376 const char* childKey;
2377 entry = entry->fParent;
2378 res = entry->fData.rootRes;
2379 Resource newRes = res_findResource(&entry->fData, res, &localPathAsCharPtr, &childKey);
2380 if (newRes != RES_BOGUS) {
2381 aliasedValue.setData(entry->fData);
2382 // TODO: do I also need to call aliasedValue.setValueLocaleDataEntry() ?
2383 aliasedValue.setResource(newRes, ResourceTracer(aliasRB)); // probably wrong to use aliasRB here
2384 aliasedValueType = aliasedValue.getType();
2385 if (aliasedValueType == URES_ALIAS) {
2386 // in a few rare cases, when we get to the root resource bundle, the resource in question
2387 // won't be an actual table, but will instead be an alias to a table. That is, we have
2388 // two aliases in the inheritance path. (For some locales, such as Zulu, we see this with
2389 // children of the "fields" resource: "day-narrow" aliases to "day-short", which aliases
2390 // to "day".) When this happens, we need to make sure we follow all the aliases.
2391 ResourceDataValue& rdv2 = static_cast<ResourceDataValue&>(aliasedValue);
2392 aliasRB = getAliasTargetAsResourceBundle(rdv2.getData(), rdv2.getResource(), nullptr, -1,
2393 rdv2.getValidLocaleDataEntry(), nullptr, 0,
2394 stackTempBundle.getAlias(), &errorCode);
2395 tablePath.clear();
2396 tablePath.append(aliasRB->fResPath, errorCode);
2397 entry = aliasRB->fData;
2398 res = aliasRB->fRes;
2399 aliasedValue.setData(entry->fData);
2400 // TODO: do I also need to call aliasedValue.setValueLocaleDataEntry() ?
2401 aliasedValue.setResource(res, ResourceTracer(aliasRB)); // probably wrong to use aliasRB here
2402 aliasedValueType = aliasedValue.getType();
2403 }
2404 if (aliasedValueType == URES_TABLE) {
2405 dest.put(parentKey, aliasedValue, isRoot, errorCode);
2406 } else {
2407 // once we've followed the alias, the resource we're looking at really should
2408 // be a table
2409 errorCode = U_INTERNAL_PROGRAM_ERROR;
2410 return;
2411 }
2412 }
2413 }
2414 }
2415 }
2416 } else {
2417 dest.put(key, value, isRoot, errorCode);
2418 }
2419 if (U_FAILURE(errorCode)) { return; }
2420 }
2421 }
2422 };
2423
2424 // Virtual destructors must be defined out of line.
~GetAllChildrenSink()2425 GetAllChildrenSink::~GetAllChildrenSink() {}
2426
2427 U_CAPI void U_EXPORT2
ures_getAllChildrenWithFallback(const UResourceBundle * bundle,const char * path,icu::ResourceSink & sink,UErrorCode & errorCode)2428 ures_getAllChildrenWithFallback(const UResourceBundle *bundle, const char *path,
2429 icu::ResourceSink &sink, UErrorCode &errorCode) {
2430 GetAllChildrenSink allChildrenSink(sink);
2431 ures_getAllItemsWithFallback(bundle, path, allChildrenSink, errorCode);
2432 }
2433
2434 } // namespace
2435
2436 // Requires a ResourceDataValue fill-in, so that we need not cast from a ResourceValue.
2437 // Unfortunately, the caller must know which subclass to make and pass in.
2438 // Alternatively, we could make it as polymorphic as in Java by
2439 // returning a ResourceValue pointer (possibly wrapped into a LocalPointer)
2440 // that the caller then owns.
2441 //
2442 // Also requires a UResourceBundle fill-in, so that the value's ResourceTracer
2443 // can point to a non-local bundle.
2444 // Without tracing, the child bundle could be a function-local object.
2445 U_CAPI void U_EXPORT2
ures_getValueWithFallback(const UResourceBundle * bundle,const char * path,UResourceBundle * tempFillIn,ResourceDataValue & value,UErrorCode & errorCode)2446 ures_getValueWithFallback(const UResourceBundle *bundle, const char *path,
2447 UResourceBundle *tempFillIn,
2448 ResourceDataValue &value, UErrorCode &errorCode) {
2449 if (U_FAILURE(errorCode)) { return; }
2450 if (path == nullptr) {
2451 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
2452 return;
2453 }
2454 const UResourceBundle *rb;
2455 if (*path == 0) {
2456 // empty path
2457 rb = bundle;
2458 } else {
2459 rb = ures_getByKeyWithFallback(bundle, path, tempFillIn, &errorCode);
2460 if (U_FAILURE(errorCode)) {
2461 return;
2462 }
2463 }
2464 value.setData(rb->getResData());
2465 value.setValidLocaleDataEntry(rb->fValidLocaleDataEntry);
2466 value.setResource(rb->fRes, ResourceTracer(rb));
2467 }
2468
2469 U_CAPI void U_EXPORT2
ures_getAllItemsWithFallback(const UResourceBundle * bundle,const char * path,icu::ResourceSink & sink,UErrorCode & errorCode)2470 ures_getAllItemsWithFallback(const UResourceBundle *bundle, const char *path,
2471 icu::ResourceSink &sink, UErrorCode &errorCode) {
2472 if (U_FAILURE(errorCode)) { return; }
2473 if (path == nullptr) {
2474 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
2475 return;
2476 }
2477 StackUResourceBundle stackBundle;
2478 const UResourceBundle *rb;
2479 if (*path == 0) {
2480 // empty path
2481 rb = bundle;
2482 } else {
2483 rb = ures_getByKeyWithFallback(bundle, path, stackBundle.getAlias(), &errorCode);
2484 if (U_FAILURE(errorCode)) {
2485 return;
2486 }
2487 }
2488 // Get all table items with fallback.
2489 ResourceDataValue value;
2490 getAllItemsWithFallback(rb, value, sink, errorCode);
2491 }
2492
ures_getByKey(const UResourceBundle * resB,const char * inKey,UResourceBundle * fillIn,UErrorCode * status)2493 U_CAPI UResourceBundle* U_EXPORT2 ures_getByKey(const UResourceBundle *resB, const char* inKey, UResourceBundle *fillIn, UErrorCode *status) {
2494 Resource res = RES_BOGUS;
2495 UResourceDataEntry *dataEntry = nullptr;
2496 const char *key = inKey;
2497
2498 if (status==nullptr || U_FAILURE(*status)) {
2499 return fillIn;
2500 }
2501 if(resB == nullptr) {
2502 *status = U_ILLEGAL_ARGUMENT_ERROR;
2503 return fillIn;
2504 }
2505
2506 int32_t type = RES_GET_TYPE(resB->fRes);
2507 if(URES_IS_TABLE(type)) {
2508 int32_t t;
2509 res = res_getTableItemByKey(&resB->getResData(), resB->fRes, &t, &key);
2510 if(res == RES_BOGUS) {
2511 key = inKey;
2512 if(resB->fHasFallback) {
2513 dataEntry = getFallbackData(resB, &key, &res, status);
2514 if(U_SUCCESS(*status)) {
2515 /* check if resB->fResPath gives the right name here */
2516 return init_resb_result(dataEntry, res, key, -1, resB, fillIn, status);
2517 } else {
2518 *status = U_MISSING_RESOURCE_ERROR;
2519 }
2520 } else {
2521 *status = U_MISSING_RESOURCE_ERROR;
2522 }
2523 } else {
2524 return init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
2525 }
2526 }
2527 #if 0
2528 /* this is a kind of TODO item. If we have an array with an index table, we could do this. */
2529 /* not currently */
2530 else if(RES_GET_TYPE(resB->fRes) == URES_ARRAY && resB->fHasFallback == true) {
2531 /* here should go a first attempt to locate the key using index table */
2532 dataEntry = getFallbackData(resB, &key, &res, status);
2533 if(U_SUCCESS(*status)) {
2534 return init_resb_result(dataEntry, res, key, resB, fillIn, status);
2535 } else {
2536 *status = U_MISSING_RESOURCE_ERROR;
2537 }
2538 }
2539 #endif
2540 else {
2541 *status = U_RESOURCE_TYPE_MISMATCH;
2542 }
2543 return fillIn;
2544 }
2545
ures_getStringByKey(const UResourceBundle * resB,const char * inKey,int32_t * len,UErrorCode * status)2546 U_CAPI const char16_t* U_EXPORT2 ures_getStringByKey(const UResourceBundle *resB, const char* inKey, int32_t* len, UErrorCode *status) {
2547 Resource res = RES_BOGUS;
2548 UResourceDataEntry *dataEntry = nullptr;
2549 const char* key = inKey;
2550
2551 if (status==nullptr || U_FAILURE(*status)) {
2552 return nullptr;
2553 }
2554 if(resB == nullptr) {
2555 *status = U_ILLEGAL_ARGUMENT_ERROR;
2556 return nullptr;
2557 }
2558
2559 int32_t type = RES_GET_TYPE(resB->fRes);
2560 if(URES_IS_TABLE(type)) {
2561 int32_t t=0;
2562
2563 res = res_getTableItemByKey(&resB->getResData(), resB->fRes, &t, &key);
2564
2565 if(res == RES_BOGUS) {
2566 key = inKey;
2567 if(resB->fHasFallback) {
2568 dataEntry = getFallbackData(resB, &key, &res, status);
2569 if(U_SUCCESS(*status)) {
2570 switch (RES_GET_TYPE(res)) {
2571 case URES_STRING:
2572 case URES_STRING_V2:
2573 return res_getString({resB, key}, &dataEntry->fData, res, len);
2574 case URES_ALIAS:
2575 {
2576 const char16_t* result = 0;
2577 UResourceBundle *tempRes = ures_getByKey(resB, inKey, nullptr, status);
2578 result = ures_getString(tempRes, len, status);
2579 ures_close(tempRes);
2580 return result;
2581 }
2582 default:
2583 *status = U_RESOURCE_TYPE_MISMATCH;
2584 }
2585 } else {
2586 *status = U_MISSING_RESOURCE_ERROR;
2587 }
2588 } else {
2589 *status = U_MISSING_RESOURCE_ERROR;
2590 }
2591 } else {
2592 switch (RES_GET_TYPE(res)) {
2593 case URES_STRING:
2594 case URES_STRING_V2:
2595 return res_getString({resB, key}, &resB->getResData(), res, len);
2596 case URES_ALIAS:
2597 {
2598 const char16_t* result = 0;
2599 UResourceBundle *tempRes = ures_getByKey(resB, inKey, nullptr, status);
2600 result = ures_getString(tempRes, len, status);
2601 ures_close(tempRes);
2602 return result;
2603 }
2604 default:
2605 *status = U_RESOURCE_TYPE_MISMATCH;
2606 }
2607 }
2608 }
2609 #if 0
2610 /* this is a kind of TODO item. If we have an array with an index table, we could do this. */
2611 /* not currently */
2612 else if(RES_GET_TYPE(resB->fRes) == URES_ARRAY && resB->fHasFallback == true) {
2613 /* here should go a first attempt to locate the key using index table */
2614 dataEntry = getFallbackData(resB, &key, &res, status);
2615 if(U_SUCCESS(*status)) {
2616 // TODO: Tracing
2617 return res_getString(rd, res, len);
2618 } else {
2619 *status = U_MISSING_RESOURCE_ERROR;
2620 }
2621 }
2622 #endif
2623 else {
2624 *status = U_RESOURCE_TYPE_MISMATCH;
2625 }
2626 return nullptr;
2627 }
2628
2629 U_CAPI const char * U_EXPORT2
ures_getUTF8StringByKey(const UResourceBundle * resB,const char * key,char * dest,int32_t * pLength,UBool forceCopy,UErrorCode * status)2630 ures_getUTF8StringByKey(const UResourceBundle *resB,
2631 const char *key,
2632 char *dest, int32_t *pLength,
2633 UBool forceCopy,
2634 UErrorCode *status) {
2635 int32_t length16;
2636 const char16_t *s16 = ures_getStringByKey(resB, key, &length16, status);
2637 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
2638 }
2639
2640 /* TODO: clean from here down */
2641
2642 /**
2643 * INTERNAL: Get the name of the first real locale (not placeholder)
2644 * that has resource bundle data.
2645 */
2646 U_CAPI const char* U_EXPORT2
ures_getLocaleInternal(const UResourceBundle * resourceBundle,UErrorCode * status)2647 ures_getLocaleInternal(const UResourceBundle* resourceBundle, UErrorCode* status)
2648 {
2649 if (status==nullptr || U_FAILURE(*status)) {
2650 return nullptr;
2651 }
2652 if (!resourceBundle) {
2653 *status = U_ILLEGAL_ARGUMENT_ERROR;
2654 return nullptr;
2655 } else {
2656 return resourceBundle->fData->fName;
2657 }
2658 }
2659
2660 U_CAPI const char* U_EXPORT2
ures_getLocale(const UResourceBundle * resourceBundle,UErrorCode * status)2661 ures_getLocale(const UResourceBundle* resourceBundle,
2662 UErrorCode* status)
2663 {
2664 return ures_getLocaleInternal(resourceBundle, status);
2665 }
2666
2667
2668 U_CAPI const char* U_EXPORT2
ures_getLocaleByType(const UResourceBundle * resourceBundle,ULocDataLocaleType type,UErrorCode * status)2669 ures_getLocaleByType(const UResourceBundle* resourceBundle,
2670 ULocDataLocaleType type,
2671 UErrorCode* status) {
2672 if (status==nullptr || U_FAILURE(*status)) {
2673 return nullptr;
2674 }
2675 if (!resourceBundle) {
2676 *status = U_ILLEGAL_ARGUMENT_ERROR;
2677 return nullptr;
2678 } else {
2679 switch(type) {
2680 case ULOC_ACTUAL_LOCALE:
2681 return resourceBundle->fData->fName;
2682 case ULOC_VALID_LOCALE:
2683 return resourceBundle->fValidLocaleDataEntry->fName;
2684 case ULOC_REQUESTED_LOCALE:
2685 default:
2686 *status = U_ILLEGAL_ARGUMENT_ERROR;
2687 return nullptr;
2688 }
2689 }
2690 }
2691
ures_getName(const UResourceBundle * resB)2692 U_CFUNC const char* ures_getName(const UResourceBundle* resB) {
2693 if(resB == nullptr) {
2694 return nullptr;
2695 }
2696
2697 return resB->fData->fName;
2698 }
2699
2700 #ifdef URES_DEBUG
ures_getPath(const UResourceBundle * resB)2701 U_CFUNC const char* ures_getPath(const UResourceBundle* resB) {
2702 if(resB == nullptr) {
2703 return nullptr;
2704 }
2705
2706 return resB->fData->fPath;
2707 }
2708 #endif
2709
2710 static UResourceBundle*
ures_openWithType(UResourceBundle * r,const char * path,const char * localeID,UResOpenType openType,UErrorCode * status)2711 ures_openWithType(UResourceBundle *r, const char* path, const char* localeID,
2712 UResOpenType openType, UErrorCode* status) {
2713 if(U_FAILURE(*status)) {
2714 return nullptr;
2715 }
2716
2717 UResourceDataEntry *entry;
2718 if(openType != URES_OPEN_DIRECT) {
2719 /* first "canonicalize" the locale ID */
2720 CharString canonLocaleID;
2721 {
2722 CharStringByteSink sink(&canonLocaleID);
2723 ulocimp_getBaseName(localeID, sink, status);
2724 }
2725 if(U_FAILURE(*status)) {
2726 *status = U_ILLEGAL_ARGUMENT_ERROR;
2727 return nullptr;
2728 }
2729 entry = entryOpen(path, canonLocaleID.data(), openType, status);
2730 } else {
2731 entry = entryOpenDirect(path, localeID, status);
2732 }
2733 if(U_FAILURE(*status)) {
2734 return nullptr;
2735 }
2736 if(entry == nullptr) {
2737 *status = U_MISSING_RESOURCE_ERROR;
2738 return nullptr;
2739 }
2740
2741 UBool isStackObject;
2742 if(r == nullptr) {
2743 r = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
2744 if(r == nullptr) {
2745 entryClose(entry);
2746 *status = U_MEMORY_ALLOCATION_ERROR;
2747 return nullptr;
2748 }
2749 isStackObject = false;
2750 } else { // fill-in
2751 isStackObject = ures_isStackObject(r);
2752 ures_closeBundle(r, false);
2753 }
2754 uprv_memset(r, 0, sizeof(UResourceBundle));
2755 ures_setIsStackObject(r, isStackObject);
2756
2757 r->fValidLocaleDataEntry = r->fData = entry;
2758 r->fHasFallback = openType != URES_OPEN_DIRECT && !r->getResData().noFallback;
2759 r->fIsTopLevel = true;
2760 r->fRes = r->getResData().rootRes;
2761 r->fSize = res_countArrayItems(&r->getResData(), r->fRes);
2762 r->fIndex = -1;
2763
2764 ResourceTracer(r).traceOpen();
2765
2766 return r;
2767 }
2768
2769 U_CAPI UResourceBundle* U_EXPORT2
ures_open(const char * path,const char * localeID,UErrorCode * status)2770 ures_open(const char* path, const char* localeID, UErrorCode* status) {
2771 return ures_openWithType(nullptr, path, localeID, URES_OPEN_LOCALE_DEFAULT_ROOT, status);
2772 }
2773
2774 U_CAPI UResourceBundle* U_EXPORT2
ures_openNoDefault(const char * path,const char * localeID,UErrorCode * status)2775 ures_openNoDefault(const char* path, const char* localeID, UErrorCode* status) {
2776 return ures_openWithType(nullptr, path, localeID, URES_OPEN_LOCALE_ROOT, status);
2777 }
2778
2779 /**
2780 * Opens a resource bundle without "canonicalizing" the locale name. No fallback will be performed
2781 * or sought. However, alias substitution will happen!
2782 */
2783 U_CAPI UResourceBundle* U_EXPORT2
ures_openDirect(const char * path,const char * localeID,UErrorCode * status)2784 ures_openDirect(const char* path, const char* localeID, UErrorCode* status) {
2785 return ures_openWithType(nullptr, path, localeID, URES_OPEN_DIRECT, status);
2786 }
2787
2788 /**
2789 * Internal API: This function is used to open a resource bundle
2790 * proper fallback chaining is executed while initialization.
2791 * The result is stored in cache for later fallback search.
2792 *
2793 * Same as ures_open(), but uses the fill-in parameter and does not allocate a new bundle.
2794 */
2795 U_CAPI void U_EXPORT2
ures_openFillIn(UResourceBundle * r,const char * path,const char * localeID,UErrorCode * status)2796 ures_openFillIn(UResourceBundle *r, const char* path,
2797 const char* localeID, UErrorCode* status) {
2798 if(U_SUCCESS(*status) && r == nullptr) {
2799 *status = U_ILLEGAL_ARGUMENT_ERROR;
2800 return;
2801 }
2802 ures_openWithType(r, path, localeID, URES_OPEN_LOCALE_DEFAULT_ROOT, status);
2803 }
2804
2805 /**
2806 * Same as ures_openDirect(), but uses the fill-in parameter and does not allocate a new bundle.
2807 */
2808 U_CAPI void U_EXPORT2
ures_openDirectFillIn(UResourceBundle * r,const char * path,const char * localeID,UErrorCode * status)2809 ures_openDirectFillIn(UResourceBundle *r, const char* path, const char* localeID, UErrorCode* status) {
2810 if(U_SUCCESS(*status) && r == nullptr) {
2811 *status = U_ILLEGAL_ARGUMENT_ERROR;
2812 return;
2813 }
2814 ures_openWithType(r, path, localeID, URES_OPEN_DIRECT, status);
2815 }
2816
2817 /**
2818 * API: Counts members. For arrays and tables, returns number of resources.
2819 * For strings, returns 1.
2820 */
2821 U_CAPI int32_t U_EXPORT2
ures_countArrayItems(const UResourceBundle * resourceBundle,const char * resourceKey,UErrorCode * status)2822 ures_countArrayItems(const UResourceBundle* resourceBundle,
2823 const char* resourceKey,
2824 UErrorCode* status)
2825 {
2826 UResourceBundle resData;
2827 ures_initStackObject(&resData);
2828 if (status==nullptr || U_FAILURE(*status)) {
2829 return 0;
2830 }
2831 if(resourceBundle == nullptr) {
2832 *status = U_ILLEGAL_ARGUMENT_ERROR;
2833 return 0;
2834 }
2835 ures_getByKey(resourceBundle, resourceKey, &resData, status);
2836
2837 if(resData.getResData().data != nullptr) {
2838 int32_t result = res_countArrayItems(&resData.getResData(), resData.fRes);
2839 ures_close(&resData);
2840 return result;
2841 } else {
2842 *status = U_MISSING_RESOURCE_ERROR;
2843 ures_close(&resData);
2844 return 0;
2845 }
2846 }
2847
2848 /**
2849 * Internal function.
2850 * Return the version number associated with this ResourceBundle as a string.
2851 *
2852 * @param resourceBundle The resource bundle for which the version is checked.
2853 * @return A version number string as specified in the resource bundle or its parent.
2854 * The caller does not own this string.
2855 * @see ures_getVersion
2856 * @internal
2857 */
2858 U_CAPI const char* U_EXPORT2
ures_getVersionNumberInternal(const UResourceBundle * resourceBundle)2859 ures_getVersionNumberInternal(const UResourceBundle *resourceBundle)
2860 {
2861 if (!resourceBundle) return nullptr;
2862
2863 if(resourceBundle->fVersion == nullptr) {
2864
2865 /* If the version ID has not been built yet, then do so. Retrieve */
2866 /* the minor version from the file. */
2867 UErrorCode status = U_ZERO_ERROR;
2868 int32_t minor_len = 0;
2869 int32_t len;
2870
2871 const char16_t* minor_version = ures_getStringByKey(resourceBundle, kVersionTag, &minor_len, &status);
2872
2873 /* Determine the length of of the final version string. This is */
2874 /* the length of the major part + the length of the separator */
2875 /* (==1) + the length of the minor part (+ 1 for the zero byte at */
2876 /* the end). */
2877
2878 len = (minor_len > 0) ? minor_len : 1;
2879
2880 /* Allocate the string, and build it up. */
2881 /* + 1 for zero byte */
2882
2883
2884 ((UResourceBundle *)resourceBundle)->fVersion = (char *)uprv_malloc(1 + len);
2885 /* Check for null pointer. */
2886 if (((UResourceBundle *)resourceBundle)->fVersion == nullptr) {
2887 return nullptr;
2888 }
2889
2890 if(minor_len > 0) {
2891 u_UCharsToChars(minor_version, resourceBundle->fVersion , minor_len);
2892 resourceBundle->fVersion[len] = '\0';
2893 }
2894 else {
2895 uprv_strcpy(resourceBundle->fVersion, kDefaultMinorVersion);
2896 }
2897 }
2898
2899 return resourceBundle->fVersion;
2900 }
2901
2902 U_CAPI const char* U_EXPORT2
ures_getVersionNumber(const UResourceBundle * resourceBundle)2903 ures_getVersionNumber(const UResourceBundle* resourceBundle)
2904 {
2905 return ures_getVersionNumberInternal(resourceBundle);
2906 }
2907
ures_getVersion(const UResourceBundle * resB,UVersionInfo versionInfo)2908 U_CAPI void U_EXPORT2 ures_getVersion(const UResourceBundle* resB, UVersionInfo versionInfo) {
2909 if (!resB) return;
2910
2911 u_versionFromString(versionInfo, ures_getVersionNumberInternal(resB));
2912 }
2913
2914 /** Tree support functions *******************************/
2915 #define INDEX_LOCALE_NAME "res_index"
2916 #define INDEX_TAG "InstalledLocales"
2917 #define DEFAULT_TAG "default"
2918
2919 #if defined(URES_TREE_DEBUG)
2920 #include <stdio.h>
2921 #endif
2922
2923 typedef struct ULocalesContext {
2924 UResourceBundle installed;
2925 UResourceBundle curr;
2926 } ULocalesContext;
2927
2928 static void U_CALLCONV
ures_loc_closeLocales(UEnumeration * enumerator)2929 ures_loc_closeLocales(UEnumeration *enumerator) {
2930 ULocalesContext *ctx = (ULocalesContext *)enumerator->context;
2931 ures_close(&ctx->curr);
2932 ures_close(&ctx->installed);
2933 uprv_free(ctx);
2934 uprv_free(enumerator);
2935 }
2936
2937 static int32_t U_CALLCONV
ures_loc_countLocales(UEnumeration * en,UErrorCode *)2938 ures_loc_countLocales(UEnumeration *en, UErrorCode * /*status*/) {
2939 ULocalesContext *ctx = (ULocalesContext *)en->context;
2940 return ures_getSize(&ctx->installed);
2941 }
2942
2943 U_CDECL_BEGIN
2944
2945
2946 static const char * U_CALLCONV
ures_loc_nextLocale(UEnumeration * en,int32_t * resultLength,UErrorCode * status)2947 ures_loc_nextLocale(UEnumeration* en,
2948 int32_t* resultLength,
2949 UErrorCode* status) {
2950 ULocalesContext *ctx = (ULocalesContext *)en->context;
2951 UResourceBundle *res = &(ctx->installed);
2952 UResourceBundle *k = nullptr;
2953 const char *result = nullptr;
2954 int32_t len = 0;
2955 if(ures_hasNext(res) && (k = ures_getNextResource(res, &ctx->curr, status)) != 0) {
2956 result = ures_getKey(k);
2957 len = (int32_t)uprv_strlen(result);
2958 }
2959 if (resultLength) {
2960 *resultLength = len;
2961 }
2962 return result;
2963 }
2964
2965 static void U_CALLCONV
ures_loc_resetLocales(UEnumeration * en,UErrorCode *)2966 ures_loc_resetLocales(UEnumeration* en,
2967 UErrorCode* /*status*/) {
2968 UResourceBundle *res = &((ULocalesContext *)en->context)->installed;
2969 ures_resetIterator(res);
2970 }
2971
2972 U_CDECL_END
2973
2974 static const UEnumeration gLocalesEnum = {
2975 nullptr,
2976 nullptr,
2977 ures_loc_closeLocales,
2978 ures_loc_countLocales,
2979 uenum_unextDefault,
2980 ures_loc_nextLocale,
2981 ures_loc_resetLocales
2982 };
2983
2984
2985 U_CAPI UEnumeration* U_EXPORT2
ures_openAvailableLocales(const char * path,UErrorCode * status)2986 ures_openAvailableLocales(const char *path, UErrorCode *status)
2987 {
2988 UResourceBundle *idx = nullptr;
2989 UEnumeration *en = nullptr;
2990 ULocalesContext *myContext = nullptr;
2991
2992 if(U_FAILURE(*status)) {
2993 return nullptr;
2994 }
2995 myContext = static_cast<ULocalesContext *>(uprv_malloc(sizeof(ULocalesContext)));
2996 en = (UEnumeration *)uprv_malloc(sizeof(UEnumeration));
2997 if(!en || !myContext) {
2998 *status = U_MEMORY_ALLOCATION_ERROR;
2999 uprv_free(en);
3000 uprv_free(myContext);
3001 return nullptr;
3002 }
3003 uprv_memcpy(en, &gLocalesEnum, sizeof(UEnumeration));
3004
3005 ures_initStackObject(&myContext->installed);
3006 ures_initStackObject(&myContext->curr);
3007 idx = ures_openDirect(path, INDEX_LOCALE_NAME, status);
3008 ures_getByKey(idx, INDEX_TAG, &myContext->installed, status);
3009 if(U_SUCCESS(*status)) {
3010 #if defined(URES_TREE_DEBUG)
3011 fprintf(stderr, "Got %s::%s::[%s] : %s\n",
3012 path, INDEX_LOCALE_NAME, INDEX_TAG, ures_getKey(&myContext->installed));
3013 #endif
3014 en->context = myContext;
3015 } else {
3016 #if defined(URES_TREE_DEBUG)
3017 fprintf(stderr, "%s open failed - %s\n", path, u_errorName(*status));
3018 #endif
3019 ures_close(&myContext->installed);
3020 uprv_free(myContext);
3021 uprv_free(en);
3022 en = nullptr;
3023 }
3024
3025 ures_close(idx);
3026
3027 return en;
3028 }
3029
isLocaleInList(UEnumeration * locEnum,const char * locToSearch,UErrorCode * status)3030 static UBool isLocaleInList(UEnumeration *locEnum, const char *locToSearch, UErrorCode *status) {
3031 const char *loc;
3032 while ((loc = uenum_next(locEnum, nullptr, status)) != nullptr) {
3033 if (uprv_strcmp(loc, locToSearch) == 0) {
3034 return true;
3035 }
3036 }
3037 return false;
3038 }
3039
getParentForFunctionalEquivalent(const char * localeID,UResourceBundle * res,UResourceBundle * bund1,char * parent,int32_t parentCapacity)3040 static void getParentForFunctionalEquivalent(const char* localeID,
3041 UResourceBundle* res,
3042 UResourceBundle* bund1,
3043 char* parent,
3044 int32_t parentCapacity) {
3045 // Get parent.
3046 // First check for a parent from %%Parent resource (Note that in resource trees
3047 // such as collation, data may have different parents than in parentLocales).
3048 UErrorCode subStatus = U_ZERO_ERROR;
3049 parent[0] = '\0';
3050 if (res != NULL) {
3051 ures_getByKey(res, "%%Parent", bund1, &subStatus);
3052 if (U_SUCCESS(subStatus)) {
3053 int32_t parentLen = parentCapacity;
3054 ures_getUTF8String(bund1, parent, &parentLen, true, &subStatus);
3055 }
3056 }
3057
3058 // If none there, use normal truncation parent
3059 if (U_FAILURE(subStatus) || parent[0] == 0) {
3060 subStatus = U_ZERO_ERROR;
3061 uloc_getParent(localeID, parent, parentCapacity, &subStatus);
3062 }
3063 }
3064
3065 U_CAPI int32_t U_EXPORT2
ures_getFunctionalEquivalent(char * result,int32_t resultCapacity,const char * path,const char * resName,const char * keyword,const char * locid,UBool * isAvailable,UBool omitDefault,UErrorCode * status)3066 ures_getFunctionalEquivalent(char *result, int32_t resultCapacity,
3067 const char *path, const char *resName, const char *keyword, const char *locid,
3068 UBool *isAvailable, UBool omitDefault, UErrorCode *status)
3069 {
3070 char defVal[1024] = ""; /* default value for given locale */
3071 char defLoc[1024] = ""; /* default value for given locale */
3072 CharString base; /* base locale */
3073 char found[1024] = "";
3074 char parent[1024] = "";
3075 char full[1024] = "";
3076 UResourceBundle bund1, bund2;
3077 UResourceBundle *res = nullptr;
3078 UErrorCode subStatus = U_ZERO_ERROR;
3079 int32_t length = 0;
3080 if(U_FAILURE(*status)) return 0;
3081 CharString kwVal;
3082 {
3083 CharStringByteSink sink(&kwVal);
3084 ulocimp_getKeywordValue(locid, keyword, sink, &subStatus);
3085 }
3086 if(kwVal == DEFAULT_TAG) {
3087 kwVal.clear();
3088 }
3089 {
3090 CharStringByteSink sink(&base);
3091 ulocimp_getBaseName(locid, sink, &subStatus);
3092 }
3093 #if defined(URES_TREE_DEBUG)
3094 fprintf(stderr, "getFunctionalEquivalent: \"%s\" [%s=%s] in %s - %s\n",
3095 locid, keyword, kwVal.data(), base.data(), u_errorName(subStatus));
3096 #endif
3097 ures_initStackObject(&bund1);
3098 ures_initStackObject(&bund2);
3099
3100 base.extract(parent, UPRV_LENGTHOF(parent), subStatus);
3101 base.extract(found, UPRV_LENGTHOF(found), subStatus);
3102
3103 if(isAvailable) {
3104 UEnumeration *locEnum = ures_openAvailableLocales(path, &subStatus);
3105 *isAvailable = true;
3106 if (U_SUCCESS(subStatus)) {
3107 *isAvailable = isLocaleInList(locEnum, parent, &subStatus);
3108 }
3109 uenum_close(locEnum);
3110 }
3111
3112 if(U_FAILURE(subStatus)) {
3113 *status = subStatus;
3114 return 0;
3115 }
3116
3117 do {
3118 subStatus = U_ZERO_ERROR;
3119 res = ures_open(path, parent, &subStatus);
3120 if(((subStatus == U_USING_FALLBACK_WARNING) ||
3121 (subStatus == U_USING_DEFAULT_WARNING)) && isAvailable)
3122 {
3123 *isAvailable = false;
3124 }
3125 isAvailable = nullptr; /* only want to set this the first time around */
3126
3127 #if defined(URES_TREE_DEBUG)
3128 fprintf(stderr, "%s;%s -> %s [%s]\n", path?path:"ICUDATA", parent, u_errorName(subStatus), ures_getLocale(res, &subStatus));
3129 #endif
3130 if(U_FAILURE(subStatus)) {
3131 *status = subStatus;
3132 } else if(subStatus == U_ZERO_ERROR) {
3133 ures_getByKey(res,resName,&bund1, &subStatus);
3134 if(subStatus == U_ZERO_ERROR) {
3135 const char16_t *defUstr;
3136 int32_t defLen;
3137 /* look for default item */
3138 #if defined(URES_TREE_DEBUG)
3139 fprintf(stderr, "%s;%s : loaded default -> %s\n",
3140 path?path:"ICUDATA", parent, u_errorName(subStatus));
3141 #endif
3142 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3143 if(U_SUCCESS(subStatus) && defLen) {
3144 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3145 #if defined(URES_TREE_DEBUG)
3146 fprintf(stderr, "%s;%s -> default %s=%s, %s\n",
3147 path?path:"ICUDATA", parent, keyword, defVal, u_errorName(subStatus));
3148 #endif
3149 uprv_strcpy(defLoc, parent);
3150 if(kwVal.isEmpty()) {
3151 kwVal.append(defVal, defLen, subStatus);
3152 #if defined(URES_TREE_DEBUG)
3153 fprintf(stderr, "%s;%s -> kwVal = %s\n",
3154 path?path:"ICUDATA", parent, keyword, kwVal.data());
3155 #endif
3156 }
3157 }
3158 }
3159 }
3160
3161 subStatus = U_ZERO_ERROR;
3162
3163 if (res != nullptr) {
3164 uprv_strcpy(found, ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus));
3165 }
3166
3167 if (uprv_strcmp(found, parent) != 0) {
3168 uprv_strcpy(parent, found);
3169 } else {
3170 getParentForFunctionalEquivalent(found,res,&bund1,parent,sizeof(parent));
3171 }
3172 ures_close(res);
3173 } while(!defVal[0] && *found && uprv_strcmp(found, "root") != 0 && U_SUCCESS(*status));
3174
3175 /* Now, see if we can find the kwVal collator.. start the search over.. */
3176 base.extract(parent, UPRV_LENGTHOF(parent), subStatus);
3177 base.extract(found, UPRV_LENGTHOF(found), subStatus);
3178
3179 do {
3180 res = ures_open(path, parent, &subStatus);
3181 if((subStatus == U_USING_FALLBACK_WARNING) && isAvailable) {
3182 *isAvailable = false;
3183 }
3184 isAvailable = nullptr; /* only want to set this the first time around */
3185
3186 #if defined(URES_TREE_DEBUG)
3187 fprintf(stderr, "%s;%s -> %s (looking for %s)\n",
3188 path?path:"ICUDATA", parent, u_errorName(subStatus), kwVal.data());
3189 #endif
3190 if(U_FAILURE(subStatus)) {
3191 *status = subStatus;
3192 } else if(subStatus == U_ZERO_ERROR) {
3193 ures_getByKey(res,resName,&bund1, &subStatus);
3194 #if defined(URES_TREE_DEBUG)
3195 /**/ fprintf(stderr,"@%d [%s] %s\n", __LINE__, resName, u_errorName(subStatus));
3196 #endif
3197 if(subStatus == U_ZERO_ERROR) {
3198 ures_getByKey(&bund1, kwVal.data(), &bund2, &subStatus);
3199 #if defined(URES_TREE_DEBUG)
3200 /**/ fprintf(stderr,"@%d [%s] %s\n", __LINE__, kwVal.data(), u_errorName(subStatus));
3201 #endif
3202 if(subStatus == U_ZERO_ERROR) {
3203 #if defined(URES_TREE_DEBUG)
3204 fprintf(stderr, "%s;%s -> full0 %s=%s, %s\n",
3205 path?path:"ICUDATA", parent, keyword, kwVal.data(), u_errorName(subStatus));
3206 #endif
3207 uprv_strcpy(full, parent);
3208 if(*full == 0) {
3209 uprv_strcpy(full, "root");
3210 }
3211 /* now, recalculate default kw if need be */
3212 if(uprv_strlen(defLoc) > uprv_strlen(full)) {
3213 const char16_t *defUstr;
3214 int32_t defLen;
3215 /* look for default item */
3216 #if defined(URES_TREE_DEBUG)
3217 fprintf(stderr, "%s;%s -> recalculating Default0\n",
3218 path?path:"ICUDATA", full);
3219 #endif
3220 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3221 if(U_SUCCESS(subStatus) && defLen) {
3222 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3223 #if defined(URES_TREE_DEBUG)
3224 fprintf(stderr, "%s;%s -> default0 %s=%s, %s\n",
3225 path?path:"ICUDATA", full, keyword, defVal, u_errorName(subStatus));
3226 #endif
3227 uprv_strcpy(defLoc, full);
3228 }
3229 } /* end of recalculate default KW */
3230 #if defined(URES_TREE_DEBUG)
3231 else {
3232 fprintf(stderr, "No trim0, %s <= %s\n", defLoc, full);
3233 }
3234 #endif
3235 } else {
3236 #if defined(URES_TREE_DEBUG)
3237 fprintf(stderr, "err=%s in %s looking for %s\n",
3238 u_errorName(subStatus), parent, kwVal.data());
3239 #endif
3240 }
3241 }
3242 }
3243
3244 UBool haveFound = false;
3245 // At least for collations which may be aliased, we need to use the VALID locale
3246 // as the parent instead of just truncating, as long as the VALID locale is not
3247 // root and has a different language than the parent. Use of the VALID locale
3248 // here is similar to the procedure used at the end of the previous do-while loop
3249 // for all resource types.
3250 if (res != NULL && uprv_strcmp(resName, "collations") == 0) {
3251 subStatus = U_ZERO_ERROR;
3252 const char *validLoc = ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus);
3253 if (U_SUCCESS(subStatus) && validLoc != NULL && validLoc[0] != 0 && uprv_strcmp(validLoc, "root") != 0) {
3254 char validLang[ULOC_LANG_CAPACITY];
3255 char parentLang[ULOC_LANG_CAPACITY];
3256 uloc_getLanguage(validLoc, validLang, ULOC_LANG_CAPACITY, &subStatus);
3257 uloc_getLanguage(parent, parentLang, ULOC_LANG_CAPACITY, &subStatus);
3258 if (U_SUCCESS(subStatus) && uprv_strcmp(validLang, parentLang) != 0) {
3259 // validLoc is not root and has a different language than parent, use it instead
3260 uprv_strcpy(found, validLoc);
3261 haveFound = true;
3262 }
3263 }
3264 subStatus = U_ZERO_ERROR;
3265 }
3266 if (!haveFound) {
3267 uprv_strcpy(found, parent);
3268 }
3269
3270 getParentForFunctionalEquivalent(found,res,&bund1,parent,1023);
3271 ures_close(res);
3272 subStatus = U_ZERO_ERROR;
3273 } while(!full[0] && *found && U_SUCCESS(*status));
3274
3275 if((full[0]==0) && kwVal != defVal) {
3276 #if defined(URES_TREE_DEBUG)
3277 fprintf(stderr, "Failed to locate kw %s - try default %s\n", kwVal.data(), defVal);
3278 #endif
3279 kwVal.clear().append(defVal, subStatus);
3280 base.extract(parent, UPRV_LENGTHOF(parent), subStatus);
3281 base.extract(found, UPRV_LENGTHOF(found), subStatus);
3282
3283 do { /* search for 'default' named item */
3284 res = ures_open(path, parent, &subStatus);
3285 if((subStatus == U_USING_FALLBACK_WARNING) && isAvailable) {
3286 *isAvailable = false;
3287 }
3288 isAvailable = nullptr; /* only want to set this the first time around */
3289
3290 #if defined(URES_TREE_DEBUG)
3291 fprintf(stderr, "%s;%s -> %s (looking for default %s)\n",
3292 path?path:"ICUDATA", parent, u_errorName(subStatus), kwVal.data());
3293 #endif
3294 if(U_FAILURE(subStatus)) {
3295 *status = subStatus;
3296 } else if(subStatus == U_ZERO_ERROR) {
3297 ures_getByKey(res,resName,&bund1, &subStatus);
3298 if(subStatus == U_ZERO_ERROR) {
3299 ures_getByKey(&bund1, kwVal.data(), &bund2, &subStatus);
3300 if(subStatus == U_ZERO_ERROR) {
3301 #if defined(URES_TREE_DEBUG)
3302 fprintf(stderr, "%s;%s -> full1 %s=%s, %s\n", path?path:"ICUDATA",
3303 parent, keyword, kwVal.data(), u_errorName(subStatus));
3304 #endif
3305 uprv_strcpy(full, parent);
3306 if(*full == 0) {
3307 uprv_strcpy(full, "root");
3308 }
3309
3310 /* now, recalculate default kw if need be */
3311 if(uprv_strlen(defLoc) > uprv_strlen(full)) {
3312 const char16_t *defUstr;
3313 int32_t defLen;
3314 /* look for default item */
3315 #if defined(URES_TREE_DEBUG)
3316 fprintf(stderr, "%s;%s -> recalculating Default1\n",
3317 path?path:"ICUDATA", full);
3318 #endif
3319 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3320 if(U_SUCCESS(subStatus) && defLen) {
3321 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3322 #if defined(URES_TREE_DEBUG)
3323 fprintf(stderr, "%s;%s -> default %s=%s, %s\n",
3324 path?path:"ICUDATA", full, keyword, defVal, u_errorName(subStatus));
3325 #endif
3326 uprv_strcpy(defLoc, full);
3327 }
3328 } /* end of recalculate default KW */
3329 #if defined(URES_TREE_DEBUG)
3330 else {
3331 fprintf(stderr, "No trim1, %s <= %s\n", defLoc, full);
3332 }
3333 #endif
3334 }
3335 }
3336 }
3337
3338 uprv_strcpy(found, parent);
3339 getParentForFunctionalEquivalent(found,res,&bund1,parent,1023);
3340 ures_close(res);
3341 subStatus = U_ZERO_ERROR;
3342 } while(!full[0] && *found && U_SUCCESS(*status));
3343 }
3344
3345 if(U_SUCCESS(*status)) {
3346 if(!full[0]) {
3347 #if defined(URES_TREE_DEBUG)
3348 fprintf(stderr, "Still could not load keyword %s=%s\n", keyword, kwVal.data());
3349 #endif
3350 *status = U_MISSING_RESOURCE_ERROR;
3351 } else if(omitDefault) {
3352 #if defined(URES_TREE_DEBUG)
3353 fprintf(stderr,"Trim? full=%s, defLoc=%s, found=%s\n", full, defLoc, found);
3354 #endif
3355 if(uprv_strlen(defLoc) <= uprv_strlen(full)) {
3356 /* found the keyword in a *child* of where the default tag was present. */
3357 if(kwVal == defVal) { /* if the requested kw is default, */
3358 /* and the default is in or in an ancestor of the current locale */
3359 #if defined(URES_TREE_DEBUG)
3360 fprintf(stderr, "Removing unneeded var %s=%s\n", keyword, kwVal.data());
3361 #endif
3362 kwVal.clear();
3363 }
3364 }
3365 }
3366 uprv_strcpy(found, full);
3367 if(!kwVal.isEmpty()) {
3368 uprv_strcat(found, "@");
3369 uprv_strcat(found, keyword);
3370 uprv_strcat(found, "=");
3371 uprv_strcat(found, kwVal.data());
3372 } else if(!omitDefault) {
3373 uprv_strcat(found, "@");
3374 uprv_strcat(found, keyword);
3375 uprv_strcat(found, "=");
3376 uprv_strcat(found, defVal);
3377 }
3378 }
3379 /* we found the default locale - no need to repeat it.*/
3380
3381 ures_close(&bund1);
3382 ures_close(&bund2);
3383
3384 length = (int32_t)uprv_strlen(found);
3385
3386 if(U_SUCCESS(*status)) {
3387 int32_t copyLength = uprv_min(length, resultCapacity);
3388 if(copyLength>0) {
3389 uprv_strncpy(result, found, copyLength);
3390 }
3391 if(length == 0) {
3392 *status = U_MISSING_RESOURCE_ERROR;
3393 }
3394 } else {
3395 length = 0;
3396 result[0]=0;
3397 }
3398 return u_terminateChars(result, resultCapacity, length, status);
3399 }
3400
3401 U_CAPI UEnumeration* U_EXPORT2
ures_getKeywordValues(const char * path,const char * keyword,UErrorCode * status)3402 ures_getKeywordValues(const char *path, const char *keyword, UErrorCode *status)
3403 {
3404 #define VALUES_BUF_SIZE 2048
3405 #define VALUES_LIST_SIZE 512
3406
3407 char valuesBuf[VALUES_BUF_SIZE];
3408 int32_t valuesIndex = 0;
3409 const char *valuesList[VALUES_LIST_SIZE];
3410 int32_t valuesCount = 0;
3411
3412 const char *locale;
3413 int32_t locLen;
3414
3415 UEnumeration *locs = nullptr;
3416
3417 UResourceBundle item;
3418 UResourceBundle subItem;
3419
3420 ures_initStackObject(&item);
3421 ures_initStackObject(&subItem);
3422 locs = ures_openAvailableLocales(path, status);
3423
3424 if(U_FAILURE(*status)) {
3425 ures_close(&item);
3426 ures_close(&subItem);
3427 return nullptr;
3428 }
3429
3430 valuesBuf[0]=0;
3431 valuesBuf[1]=0;
3432
3433 while((locale = uenum_next(locs, &locLen, status)) != 0) {
3434 UResourceBundle *bund = nullptr;
3435 UResourceBundle *subPtr = nullptr;
3436 UErrorCode subStatus = U_ZERO_ERROR; /* don't fail if a bundle is unopenable */
3437 bund = ures_open(path, locale, &subStatus);
3438
3439 #if defined(URES_TREE_DEBUG)
3440 if(!bund || U_FAILURE(subStatus)) {
3441 fprintf(stderr, "%s-%s values: Can't open %s locale - skipping. (%s)\n",
3442 path?path:"<ICUDATA>", keyword, locale, u_errorName(subStatus));
3443 }
3444 #endif
3445
3446 ures_getByKey(bund, keyword, &item, &subStatus);
3447
3448 if(!bund || U_FAILURE(subStatus)) {
3449 #if defined(URES_TREE_DEBUG)
3450 fprintf(stderr, "%s-%s values: Can't find in %s - skipping. (%s)\n",
3451 path?path:"<ICUDATA>", keyword, locale, u_errorName(subStatus));
3452 #endif
3453 ures_close(bund);
3454 bund = nullptr;
3455 continue;
3456 }
3457
3458 while((subPtr = ures_getNextResource(&item,&subItem,&subStatus)) != 0
3459 && U_SUCCESS(subStatus)) {
3460 const char *k;
3461 int32_t i;
3462 k = ures_getKey(subPtr);
3463
3464 #if defined(URES_TREE_DEBUG)
3465 /* fprintf(stderr, "%s | %s | %s | %s\n", path?path:"<ICUDATA>", keyword, locale, k); */
3466 #endif
3467 if(k == nullptr || *k == 0 ||
3468 uprv_strcmp(k, DEFAULT_TAG) == 0 || uprv_strncmp(k, "private-", 8) == 0) {
3469 // empty or "default" or unlisted type
3470 continue;
3471 }
3472 for(i=0; i<valuesCount; i++) {
3473 if(!uprv_strcmp(valuesList[i],k)) {
3474 k = nullptr; /* found duplicate */
3475 break;
3476 }
3477 }
3478 if(k != nullptr) {
3479 int32_t kLen = (int32_t)uprv_strlen(k);
3480 if((valuesCount >= (VALUES_LIST_SIZE-1)) || /* no more space in list .. */
3481 ((valuesIndex+kLen+1+1) >= VALUES_BUF_SIZE)) { /* no more space in buffer (string + 2 nulls) */
3482 *status = U_ILLEGAL_ARGUMENT_ERROR; /* out of space.. */
3483 } else {
3484 uprv_strcpy(valuesBuf+valuesIndex, k);
3485 valuesList[valuesCount++] = valuesBuf+valuesIndex;
3486 valuesIndex += kLen;
3487 #if defined(URES_TREE_DEBUG)
3488 fprintf(stderr, "%s | %s | %s | [%s] (UNIQUE)\n",
3489 path?path:"<ICUDATA>", keyword, locale, k);
3490 #endif
3491 valuesBuf[valuesIndex++] = 0; /* terminate */
3492 }
3493 }
3494 }
3495 ures_close(bund);
3496 }
3497 valuesBuf[valuesIndex++] = 0; /* terminate */
3498
3499 ures_close(&item);
3500 ures_close(&subItem);
3501 uenum_close(locs);
3502 #if defined(URES_TREE_DEBUG)
3503 fprintf(stderr, "%s: size %d, #%d\n", u_errorName(*status),
3504 valuesIndex, valuesCount);
3505 #endif
3506 return uloc_openKeywordList(valuesBuf, valuesIndex, status);
3507 }
3508 #if 0
3509 /* This code isn't needed, and given the documentation warnings the implementation is suspect */
3510 U_CAPI UBool U_EXPORT2
3511 ures_equal(const UResourceBundle* res1, const UResourceBundle* res2){
3512 if(res1==nullptr || res2==nullptr){
3513 return res1==res2; /* pointer comparison */
3514 }
3515 if(res1->fKey==nullptr|| res2->fKey==nullptr){
3516 return (res1->fKey==res2->fKey);
3517 }else{
3518 if(uprv_strcmp(res1->fKey, res2->fKey)!=0){
3519 return false;
3520 }
3521 }
3522 if(uprv_strcmp(res1->fData->fName, res2->fData->fName)!=0){
3523 return false;
3524 }
3525 if(res1->fData->fPath == nullptr|| res2->fData->fPath==nullptr){
3526 return (res1->fData->fPath == res2->fData->fPath);
3527 }else{
3528 if(uprv_strcmp(res1->fData->fPath, res2->fData->fPath)!=0){
3529 return false;
3530 }
3531 }
3532 if(uprv_strcmp(res1->fData->fParent->fName, res2->fData->fParent->fName)!=0){
3533 return false;
3534 }
3535 if(uprv_strcmp(res1->fData->fParent->fPath, res2->fData->fParent->fPath)!=0){
3536 return false;
3537 }
3538 if(uprv_strncmp(res1->fResPath, res2->fResPath, res1->fResPathLen)!=0){
3539 return false;
3540 }
3541 if(res1->fRes != res2->fRes){
3542 return false;
3543 }
3544 return true;
3545 }
3546 U_CAPI UResourceBundle* U_EXPORT2
3547 ures_clone(const UResourceBundle* res, UErrorCode* status){
3548 UResourceBundle* bundle = nullptr;
3549 UResourceBundle* ret = nullptr;
3550 if(U_FAILURE(*status) || res == nullptr){
3551 return nullptr;
3552 }
3553 bundle = ures_open(res->fData->fPath, res->fData->fName, status);
3554 if(res->fResPath!=nullptr){
3555 ret = ures_findSubResource(bundle, res->fResPath, nullptr, status);
3556 ures_close(bundle);
3557 }else{
3558 ret = bundle;
3559 }
3560 return ret;
3561 }
3562 U_CAPI const UResourceBundle* U_EXPORT2
3563 ures_getParentBundle(const UResourceBundle* res){
3564 if(res==nullptr){
3565 return nullptr;
3566 }
3567 return res->fParentRes;
3568 }
3569 #endif
3570
3571 U_CAPI void U_EXPORT2
ures_getVersionByKey(const UResourceBundle * res,const char * key,UVersionInfo ver,UErrorCode * status)3572 ures_getVersionByKey(const UResourceBundle* res, const char *key, UVersionInfo ver, UErrorCode *status) {
3573 const char16_t *str;
3574 int32_t len;
3575 str = ures_getStringByKey(res, key, &len, status);
3576 if(U_SUCCESS(*status)) {
3577 u_versionFromUString(ver, str);
3578 }
3579 }
3580
3581 /* eof */
3582