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