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