blob: 17c0177a05cb5ec9f4b0cc57956885b72965df8a [file] [log] [blame]
Jungshik Shin87232d82017-05-13 21:10:13 -07001// © 2016 and later: Unicode, Inc. and others.
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002// License & terms of use: http://www.unicode.org/copyright.html
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003/*
4******************************************************************************
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07005* Copyright (C) 1997-2016, International Business Machines Corporation and
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00006* others. All Rights Reserved.
7******************************************************************************
8*
Jungshik Shin70f82502016-01-29 00:32:36 -08009* File uresbund.cpp
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000010*
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
Frank Tang69c72a62019-04-03 21:41:21 -070024#include "unicode/ures.h"
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000025#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"
Frank Tangb8696612019-10-25 14:58:21 -070034#include "mutex.h"
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000035#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"
Frank Tang952ccb92019-08-22 12:09:17 -070042#include "uresdata.h"
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000043
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -080044using namespace icu;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000045
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 = NULL;
Frank Tang1c67b4e2022-05-18 10:13:51 -070052static icu::UInitOnce gCacheInitOnce {};
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000053
Frank Tangb8696612019-10-25 14:58:21 -070054static UMutex resbMutex;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000055
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;
Jungshik Shin70f82502016-01-29 00:32:36 -080062 return uhash_hashChars(namekey)+37u*uhash_hashChars(pathkey);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000063}
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 != NULL) {
87 *i = '\0';
Frank Tang1f164ee2022-11-08 12:31:27 -080088 return true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +000089 }
90
Frank Tang1f164ee2022-11-08 12:31:27 -080091 return false;
92}
93
94static UBool hasVariant(const char* localeID) {
95 UErrorCode err = U_ZERO_ERROR;
96 int32_t variantLength = uloc_getVariant(localeID, NULL, 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 if (name[uprv_strlen(name) - 1] == '_' || hasVariant(name)) {
206 return chopLocale(name);
207 }
208
209 UErrorCode err = U_ZERO_ERROR;
210 const char* tempNamePtr = name;
211 CharString language = ulocimp_getLanguage(tempNamePtr, &tempNamePtr, err);
212 if (*tempNamePtr == '_') {
213 ++tempNamePtr;
214 }
215 CharString script = ulocimp_getScript(tempNamePtr, &tempNamePtr, err);
216 if (*tempNamePtr == '_') {
217 ++tempNamePtr;
218 }
219 CharString region = ulocimp_getCountry(tempNamePtr, &tempNamePtr, err);
220 CharString workingLocale;
221 if (U_FAILURE(err)) {
222 // hopefully this never happens...
223 return chopLocale(name);
224 }
225
226 // if the open type is URES_OPEN_LOCALE_DEFAULT_ROOT, first look the locale ID up in the parent locale table;
227 // if that table specifies a parent for it, return that (we don't do this for the other open types-- if we're not
228 // falling back through the system default locale, we also want to do straight truncation fallback instead
229 // of looking things up in the parent locale table-- see https://www.unicode.org/reports/tr35/tr35.html#Parent_Locales:
230 // "Collation data, however, is an exception...")
231 if (openType == URES_OPEN_LOCALE_DEFAULT_ROOT) {
232 const char* parentID = performFallbackLookup(name, parentLocaleChars, parentLocaleChars, parentLocaleTable, UPRV_LENGTHOF(parentLocaleTable));
233 if (parentID != NULL) {
234 uprv_strcpy(name, parentID);
235 return true;
236 }
237 }
238
239 // if it's not in the parent locale table, figure out the fallback script algorithmically
240 // (see CLDR-15265 for an explanation of the algorithm)
241 if (!script.isEmpty() && !region.isEmpty()) {
242 // if "name" has both script and region, is the script the default script?
243 // - if so, remove it and keep the region
244 // - if not, remove the region and keep the script
245 if (getDefaultScript(language, region) == script.toStringPiece()) {
246 workingLocale.append(language, err).append("_", err).append(region, err);
247 } else {
248 workingLocale.append(language, err).append("_", err).append(script, err);
249 }
250 } else if (!region.isEmpty()) {
251 // if "name" has region but not script, did the original locale ID specify a script?
252 // - if yes, replace the region with the script from the original locale ID
253 // - if no, replace the region with the default script for that language and region
254 UErrorCode err = U_ZERO_ERROR;
255 tempNamePtr = origName;
256 CharString origNameLanguage = ulocimp_getLanguage(tempNamePtr, &tempNamePtr, err);
257 if (*tempNamePtr == '_') {
258 ++tempNamePtr;
259 }
260 CharString origNameScript = ulocimp_getScript(origName, nullptr, err);
261 if (!origNameScript.isEmpty()) {
262 workingLocale.append(language, err).append("_", err).append(origNameScript, err);
263 } else {
264 workingLocale.append(language, err).append("_", err).append(getDefaultScript(language, region), err);
265 }
266 } else if (!script.isEmpty()) {
267 // if "name" has script but not region (and our open type if URES_OPEN_LOCALE_DEFAULT_ROOT), is the script
268 // the default script for the language?
269 // - if so, remove it from the locale ID
270 // - if not, return false to continue up the chain
271 // (we don't do this for other open types for the same reason we don't look things up in the parent
272 // locale table for other open types-- see the reference to UTS #35 above)
273 if (openType != URES_OPEN_LOCALE_DEFAULT_ROOT || getDefaultScript(language, CharString()) == script.toStringPiece()) {
274 workingLocale.append(language, err);
275 } else {
276 return false;
277 }
278 } else {
279 // if "name" just contains a language code, return false so the calling code falls back to "root"
280 return false;
281 }
282 if (U_SUCCESS(err) && !workingLocale.isEmpty()) {
283 uprv_strcpy(name, workingLocale.data());
284 return true;
285 } else {
286 return false;
287 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000288}
289
290/**
Frank Tang7e7574b2021-04-13 21:19:13 -0700291 * Called to check whether a name without '_' needs to be checked for a parent.
292 * Some code had assumed that locale IDs with '_' could not have a non-root parent.
293 * We may want a better way of doing this.
294 */
295static UBool mayHaveParent(char *name) {
296 return (name[0] != 0 && uprv_strstr("nb nn",name) != nullptr);
297}
298
299/**
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000300 * Internal function
301 */
302static void entryIncrease(UResourceDataEntry *entry) {
Frank Tangb8696612019-10-25 14:58:21 -0700303 Mutex lock(&resbMutex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000304 entry->fCountExisting++;
305 while(entry->fParent != NULL) {
306 entry = entry->fParent;
307 entry->fCountExisting++;
308 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000309}
310
311/**
Frank Tang3e05d9d2021-11-08 14:04:04 -0800312 * Internal function. Tries to find a resource in given Resource
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000313 * Bundle, as well as in its parents
314 */
Frank Tang3e05d9d2021-11-08 14:04:04 -0800315static UResourceDataEntry *getFallbackData(
316 const UResourceBundle *resBundle,
317 const char **resTag, Resource *res, UErrorCode *status) {
318 UResourceDataEntry *dataEntry = resBundle->fData;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000319 int32_t indexR = -1;
320 int32_t i = 0;
321 *res = RES_BOGUS;
Frank Tang3e05d9d2021-11-08 14:04:04 -0800322 if(dataEntry == nullptr) {
323 *status = U_MISSING_RESOURCE_ERROR;
324 return nullptr;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000325 }
Frank Tang3e05d9d2021-11-08 14:04:04 -0800326 if(dataEntry->fBogus == U_ZERO_ERROR) { /* if this resource is real, */
327 *res = res_getTableItemByKey(&(dataEntry->fData), dataEntry->fData.rootRes, &indexR, resTag); /* try to get data from there */
328 i++;
329 }
330 if(resBundle->fHasFallback) {
331 // Otherwise, we'll look in parents.
332 while(*res == RES_BOGUS && dataEntry->fParent != nullptr) {
333 dataEntry = dataEntry->fParent;
334 if(dataEntry->fBogus == U_ZERO_ERROR) {
335 i++;
336 *res = res_getTableItemByKey(&(dataEntry->fData), dataEntry->fData.rootRes, &indexR, resTag);
337 }
338 }
339 }
340
341 if(*res == RES_BOGUS) {
342 // If the resource is not found, we need to give an error.
343 *status = U_MISSING_RESOURCE_ERROR;
344 return nullptr;
345 }
346 // If the resource is found in parents, we need to adjust the error.
347 if(i>1) {
348 if(uprv_strcmp(dataEntry->fName, uloc_getDefault())==0 || uprv_strcmp(dataEntry->fName, kRootLocaleName)==0) {
349 *status = U_USING_DEFAULT_WARNING;
350 } else {
351 *status = U_USING_FALLBACK_WARNING;
352 }
353 }
354 return dataEntry;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000355}
356
357static void
358free_entry(UResourceDataEntry *entry) {
359 UResourceDataEntry *alias;
360 res_unload(&(entry->fData));
361 if(entry->fName != NULL && entry->fName != entry->fNameBuffer) {
362 uprv_free(entry->fName);
363 }
364 if(entry->fPath != NULL) {
365 uprv_free(entry->fPath);
366 }
367 if(entry->fPool != NULL) {
368 --entry->fPool->fCountExisting;
369 }
370 alias = entry->fAlias;
371 if(alias != NULL) {
372 while(alias->fAlias != NULL) {
373 alias = alias->fAlias;
374 }
375 --alias->fCountExisting;
376 }
377 uprv_free(entry);
378}
379
380/* Works just like ucnv_flushCache() */
381static int32_t ures_flushCache()
382{
383 UResourceDataEntry *resB;
384 int32_t pos;
385 int32_t rbDeletedNum = 0;
386 const UHashElement *e;
387 UBool deletedMore;
388
389 /*if shared data hasn't even been lazy evaluated yet
390 * return 0
391 */
Frank Tangb8696612019-10-25 14:58:21 -0700392 Mutex lock(&resbMutex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000393 if (cache == NULL) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000394 return 0;
395 }
396
397 do {
Frank Tang1f164ee2022-11-08 12:31:27 -0800398 deletedMore = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000399 /*creates an enumeration to iterate through every element in the table */
Jungshik Shin70f82502016-01-29 00:32:36 -0800400 pos = UHASH_FIRST;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000401 while ((e = uhash_nextElement(cache, &pos)) != NULL)
402 {
403 resB = (UResourceDataEntry *) e->value.pointer;
404 /* Deletes only if reference counter == 0
405 * Don't worry about the children of this node.
406 * Those will eventually get deleted too, if not already.
407 * Don't worry about the parents of this node.
408 * Those will eventually get deleted too, if not already.
409 */
410 /* 04/05/2002 [weiv] fCountExisting should now be accurate. If it's not zero, that means that */
411 /* some resource bundles are still open somewhere. */
412
413 if (resB->fCountExisting == 0) {
414 rbDeletedNum++;
Frank Tang1f164ee2022-11-08 12:31:27 -0800415 deletedMore = true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000416 uhash_removeElement(cache, e);
417 free_entry(resB);
418 }
419 }
420 /*
421 * Do it again to catch bundles (aliases, pool bundle) whose fCountExisting
422 * got decremented by free_entry().
423 */
424 } while(deletedMore);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000425
426 return rbDeletedNum;
427}
428
429#ifdef URES_DEBUG
430#include <stdio.h>
431
432U_CAPI UBool U_EXPORT2 ures_dumpCacheContents(void) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800433 UBool cacheNotEmpty = false;
Jungshik Shin70f82502016-01-29 00:32:36 -0800434 int32_t pos = UHASH_FIRST;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000435 const UHashElement *e;
436 UResourceDataEntry *resB;
437
Frank Tangb8696612019-10-25 14:58:21 -0700438 Mutex lock(&resbMutex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000439 if (cache == NULL) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000440 fprintf(stderr,"%s:%d: RB Cache is NULL.\n", __FILE__, __LINE__);
Frank Tang1f164ee2022-11-08 12:31:27 -0800441 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000442 }
443
444 while ((e = uhash_nextElement(cache, &pos)) != NULL) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800445 cacheNotEmpty=true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000446 resB = (UResourceDataEntry *) e->value.pointer;
447 fprintf(stderr,"%s:%d: RB Cache: Entry @0x%p, refcount %d, name %s:%s. Pool 0x%p, alias 0x%p, parent 0x%p\n",
448 __FILE__, __LINE__,
449 (void*)resB, resB->fCountExisting,
450 resB->fName?resB->fName:"NULL",
451 resB->fPath?resB->fPath:"NULL",
452 (void*)resB->fPool,
453 (void*)resB->fAlias,
454 (void*)resB->fParent);
455 }
456
457 fprintf(stderr,"%s:%d: RB Cache still contains %d items.\n", __FILE__, __LINE__, uhash_count(cache));
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000458 return cacheNotEmpty;
459}
460
461#endif
462
463static UBool U_CALLCONV ures_cleanup(void)
464{
465 if (cache != NULL) {
466 ures_flushCache();
467 uhash_close(cache);
468 cache = NULL;
469 }
470 gCacheInitOnce.reset();
Frank Tang1f164ee2022-11-08 12:31:27 -0800471 return true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000472}
473
474/** INTERNAL: Initializes the cache for resources */
Jungshik Shin5feb9ad2016-10-21 12:52:48 -0700475static void U_CALLCONV createCache(UErrorCode &status) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000476 U_ASSERT(cache == NULL);
477 cache = uhash_open(hashEntry, compareEntries, NULL, &status);
478 ucln_common_registerCleanup(UCLN_COMMON_URES, ures_cleanup);
479}
480
481static void initCache(UErrorCode *status) {
482 umtx_initOnce(gCacheInitOnce, &createCache, *status);
483}
484
485/** INTERNAL: sets the name (locale) of the resource bundle to given name */
486
487static void setEntryName(UResourceDataEntry *res, const char *name, UErrorCode *status) {
488 int32_t len = (int32_t)uprv_strlen(name);
489 if(res->fName != NULL && res->fName != res->fNameBuffer) {
490 uprv_free(res->fName);
491 }
492 if (len < (int32_t)sizeof(res->fNameBuffer)) {
493 res->fName = res->fNameBuffer;
494 }
495 else {
496 res->fName = (char *)uprv_malloc(len+1);
497 }
498 if(res->fName == NULL) {
499 *status = U_MEMORY_ALLOCATION_ERROR;
500 } else {
501 uprv_strcpy(res->fName, name);
502 }
503}
504
505static UResourceDataEntry *
506getPoolEntry(const char *path, UErrorCode *status);
507
508/**
509 * INTERNAL: Inits and opens an entry from a data DLL.
510 * CAUTION: resbMutex must be locked when calling this function.
511 */
512static UResourceDataEntry *init_entry(const char *localeID, const char *path, UErrorCode *status) {
513 UResourceDataEntry *r = NULL;
514 UResourceDataEntry find;
515 /*int32_t hashValue;*/
516 const char *name;
517 char aliasName[100] = { 0 };
518 int32_t aliasLen = 0;
Frank Tang1f164ee2022-11-08 12:31:27 -0800519 /*UBool isAlias = false;*/
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000520 /*UHashTok hashkey; */
521
522 if(U_FAILURE(*status)) {
523 return NULL;
524 }
525
526 /* here we try to deduce the right locale name */
527 if(localeID == NULL) { /* if localeID is NULL, we're trying to open default locale */
528 name = uloc_getDefault();
529 } else if(*localeID == 0) { /* if localeID is "" then we try to open root locale */
530 name = kRootLocaleName;
531 } else { /* otherwise, we'll open what we're given */
532 name = localeID;
533 }
534
535 find.fName = (char *)name;
536 find.fPath = (char *)path;
537
538 /* calculate the hash value of the entry */
539 /*hashkey.pointer = (void *)&find;*/
540 /*hashValue = hashEntry(hashkey);*/
541
542 /* check to see if we already have this entry */
543 r = (UResourceDataEntry *)uhash_get(cache, &find);
544 if(r == NULL) {
545 /* if the entry is not yet in the hash table, we'll try to construct a new one */
546 r = (UResourceDataEntry *) uprv_malloc(sizeof(UResourceDataEntry));
547 if(r == NULL) {
548 *status = U_MEMORY_ALLOCATION_ERROR;
549 return NULL;
550 }
551
552 uprv_memset(r, 0, sizeof(UResourceDataEntry));
553 /*r->fHashKey = hashValue;*/
554
555 setEntryName(r, name, status);
556 if (U_FAILURE(*status)) {
557 uprv_free(r);
558 return NULL;
559 }
560
561 if(path != NULL) {
562 r->fPath = (char *)uprv_strdup(path);
563 if(r->fPath == NULL) {
564 *status = U_MEMORY_ALLOCATION_ERROR;
565 uprv_free(r);
566 return NULL;
567 }
568 }
569
570 /* this is the actual loading */
571 res_load(&(r->fData), r->fPath, r->fName, status);
572
Jungshik Shin42d50272018-10-24 01:22:09 -0700573 if (U_FAILURE(*status)) {
574 /* if we failed to load due to an out-of-memory error, exit early. */
575 if (*status == U_MEMORY_ALLOCATION_ERROR) {
576 uprv_free(r);
577 return NULL;
578 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000579 /* we have no such entry in dll, so it will always use fallback */
580 *status = U_USING_FALLBACK_WARNING;
581 r->fBogus = U_USING_FALLBACK_WARNING;
582 } else { /* if we have a regular entry */
583 Resource aliasres;
584 if (r->fData.usesPoolBundle) {
585 r->fPool = getPoolEntry(r->fPath, status);
586 if (U_SUCCESS(*status)) {
587 const int32_t *poolIndexes = r->fPool->fData.pRoot + 1;
588 if(r->fData.pRoot[1 + URES_INDEX_POOL_CHECKSUM] == poolIndexes[URES_INDEX_POOL_CHECKSUM]) {
589 r->fData.poolBundleKeys = (const char *)(poolIndexes + (poolIndexes[URES_INDEX_LENGTH] & 0xff));
Jungshik Shin70f82502016-01-29 00:32:36 -0800590 r->fData.poolBundleStrings = r->fPool->fData.p16BitUnits;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000591 } else {
592 r->fBogus = *status = U_INVALID_FORMAT_ERROR;
593 }
594 } else {
595 r->fBogus = *status;
596 }
597 }
598 if (U_SUCCESS(*status)) {
599 /* handle the alias by trying to get out the %%Alias tag.*/
600 /* We'll try to get alias string from the bundle */
601 aliasres = res_getResource(&(r->fData), "%%ALIAS");
602 if (aliasres != RES_BOGUS) {
Frank Tang952ccb92019-08-22 12:09:17 -0700603 // No tracing: called during initial data loading
604 const UChar *alias = res_getStringNoTrace(&(r->fData), aliasres, &aliasLen);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000605 if(alias != NULL && aliasLen > 0) { /* if there is actual alias - unload and load new data */
606 u_UCharsToChars(alias, aliasName, aliasLen+1);
607 r->fAlias = init_entry(aliasName, path, status);
608 }
609 }
610 }
611 }
612
613 {
614 UResourceDataEntry *oldR = NULL;
615 if((oldR = (UResourceDataEntry *)uhash_get(cache, r)) == NULL) { /* if the data is not cached */
616 /* just insert it in the cache */
617 UErrorCode cacheStatus = U_ZERO_ERROR;
618 uhash_put(cache, (void *)r, r, &cacheStatus);
619 if (U_FAILURE(cacheStatus)) {
620 *status = cacheStatus;
621 free_entry(r);
622 r = NULL;
623 }
624 } else {
625 /* somebody have already inserted it while we were working, discard newly opened data */
626 /* Also, we could get here IF we opened an alias */
627 free_entry(r);
628 r = oldR;
629 }
630 }
631
632 }
633 if(r != NULL) {
634 /* return the real bundle */
635 while(r->fAlias != NULL) {
636 r = r->fAlias;
637 }
638 r->fCountExisting++; /* we increase its reference count */
639 /* if the resource has a warning */
640 /* we don't want to overwrite a status with no error */
641 if(r->fBogus != U_ZERO_ERROR && U_SUCCESS(*status)) {
642 *status = r->fBogus; /* set the returning status */
643 }
644 }
645 return r;
646}
647
648static UResourceDataEntry *
649getPoolEntry(const char *path, UErrorCode *status) {
650 UResourceDataEntry *poolBundle = init_entry(kPoolBundleName, path, status);
651 if( U_SUCCESS(*status) &&
652 (poolBundle == NULL || poolBundle->fBogus != U_ZERO_ERROR || !poolBundle->fData.isPoolBundle)
653 ) {
654 *status = U_INVALID_FORMAT_ERROR;
655 }
656 return poolBundle;
657}
658
659/* INTERNAL: */
660/* CAUTION: resbMutex must be locked when calling this function! */
Jungshik Shin70f82502016-01-29 00:32:36 -0800661static UResourceDataEntry *
Frank Tang1f164ee2022-11-08 12:31:27 -0800662findFirstExisting(const char* path, char* name, const char* defaultLocale, UResOpenType openType,
663 UBool *isRoot, UBool *foundParent, UBool *isDefault, UErrorCode* status) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000664 UResourceDataEntry *r = NULL;
Frank Tang1f164ee2022-11-08 12:31:27 -0800665 UBool hasRealData = false;
666 *foundParent = true; /* we're starting with a fresh name */
667 char origName[ULOC_FULLNAME_CAPACITY];
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000668
Frank Tang1f164ee2022-11-08 12:31:27 -0800669 uprv_strcpy(origName, name);
670 while(*foundParent && !hasRealData) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000671 r = init_entry(name, path, status);
672 /* Null pointer test */
673 if (U_FAILURE(*status)) {
674 return NULL;
675 }
Frank Tang3e05d9d2021-11-08 14:04:04 -0800676 *isDefault = (UBool)(uprv_strncmp(name, defaultLocale, uprv_strlen(name)) == 0);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000677 hasRealData = (UBool)(r->fBogus == U_ZERO_ERROR);
678 if(!hasRealData) {
679 /* this entry is not real. We will discard it. */
680 /* However, the parent line for this entry is */
681 /* not to be used - as there might be parent */
682 /* lines in cache from previous openings that */
683 /* are not updated yet. */
684 r->fCountExisting--;
685 /*entryCloseInt(r);*/
686 r = NULL;
687 *status = U_USING_FALLBACK_WARNING;
688 } else {
689 uprv_strcpy(name, r->fName); /* this is needed for supporting aliases */
690 }
691
692 *isRoot = (UBool)(uprv_strcmp(name, kRootLocaleName) == 0);
693
694 /*Fallback data stuff*/
Frank Tang1f164ee2022-11-08 12:31:27 -0800695 if (!hasRealData) {
696 *foundParent = getParentLocaleID(name, origName, openType);
697 } else {
698 // we've already found a real resource file; what we return to the caller is the parent
699 // locale ID for inheritance, which should come from chopLocale(), not getParentLocaleID()
700 *foundParent = chopLocale(name);
701 }
702 if (*foundParent && *name == '\0') {
Frank Tang69c72a62019-04-03 21:41:21 -0700703 uprv_strcpy(name, "und");
704 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000705 }
706 return r;
707}
708
709static void ures_setIsStackObject( UResourceBundle* resB, UBool state) {
710 if(state) {
711 resB->fMagic1 = 0;
712 resB->fMagic2 = 0;
713 } else {
714 resB->fMagic1 = MAGIC1;
715 resB->fMagic2 = MAGIC2;
716 }
717}
718
719static UBool ures_isStackObject(const UResourceBundle* resB) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800720 return((resB->fMagic1 == MAGIC1 && resB->fMagic2 == MAGIC2)?false:true);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000721}
722
723
724U_CFUNC void ures_initStackObject(UResourceBundle* resB) {
725 uprv_memset(resB, 0, sizeof(UResourceBundle));
Frank Tang1f164ee2022-11-08 12:31:27 -0800726 ures_setIsStackObject(resB, true);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000727}
728
Frank Tang69c72a62019-04-03 21:41:21 -0700729U_NAMESPACE_BEGIN
730
731StackUResourceBundle::StackUResourceBundle() {
732 ures_initStackObject(&bundle);
733}
734
735StackUResourceBundle::~StackUResourceBundle() {
736 ures_close(&bundle);
737}
738
739U_NAMESPACE_END
740
Jungshik Shin70f82502016-01-29 00:32:36 -0800741static UBool // returns U_SUCCESS(*status)
742loadParentsExceptRoot(UResourceDataEntry *&t1,
743 char name[], int32_t nameCapacity,
744 UBool usingUSRData, char usrDataPath[], UErrorCode *status) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800745 if (U_FAILURE(*status)) { return false; }
746 UBool checkParent = true;
Frank Tang7e7574b2021-04-13 21:19:13 -0700747 while (checkParent && t1->fParent == NULL && !t1->fData.noFallback &&
Jungshik Shin70f82502016-01-29 00:32:36 -0800748 res_getResource(&t1->fData,"%%ParentIsRoot") == RES_BOGUS) {
749 Resource parentRes = res_getResource(&t1->fData, "%%Parent");
750 if (parentRes != RES_BOGUS) { // An explicit parent was found.
751 int32_t parentLocaleLen = 0;
Frank Tang952ccb92019-08-22 12:09:17 -0700752 // No tracing: called during initial data loading
753 const UChar *parentLocaleName = res_getStringNoTrace(&(t1->fData), parentRes, &parentLocaleLen);
Jungshik Shin70f82502016-01-29 00:32:36 -0800754 if(parentLocaleName != NULL && 0 < parentLocaleLen && parentLocaleLen < nameCapacity) {
755 u_UCharsToChars(parentLocaleName, name, parentLocaleLen + 1);
756 if (uprv_strcmp(name, kRootLocaleName) == 0) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800757 return true;
Jungshik Shin70f82502016-01-29 00:32:36 -0800758 }
759 }
760 }
761 // Insert regular parents.
762 UErrorCode parentStatus = U_ZERO_ERROR;
763 UResourceDataEntry *t2 = init_entry(name, t1->fPath, &parentStatus);
764 if (U_FAILURE(parentStatus)) {
765 *status = parentStatus;
Frank Tang1f164ee2022-11-08 12:31:27 -0800766 return false;
Jungshik Shin70f82502016-01-29 00:32:36 -0800767 }
768 UResourceDataEntry *u2 = NULL;
769 UErrorCode usrStatus = U_ZERO_ERROR;
770 if (usingUSRData) { // This code inserts user override data into the inheritance chain.
771 u2 = init_entry(name, usrDataPath, &usrStatus);
Jungshik Shin42d50272018-10-24 01:22:09 -0700772 // If we failed due to out-of-memory, report that to the caller and exit early.
773 if (usrStatus == U_MEMORY_ALLOCATION_ERROR) {
774 *status = usrStatus;
Frank Tang1f164ee2022-11-08 12:31:27 -0800775 return false;
Jungshik Shin42d50272018-10-24 01:22:09 -0700776 }
Jungshik Shin70f82502016-01-29 00:32:36 -0800777 }
778
779 if (usingUSRData && U_SUCCESS(usrStatus) && u2->fBogus == U_ZERO_ERROR) {
780 t1->fParent = u2;
781 u2->fParent = t2;
782 } else {
783 t1->fParent = t2;
784 if (usingUSRData) {
785 // The USR override data wasn't found, set it to be deleted.
786 u2->fCountExisting = 0;
787 }
788 }
789 t1 = t2;
Frank Tang7e7574b2021-04-13 21:19:13 -0700790 checkParent = chopLocale(name) || mayHaveParent(name);
Jungshik Shin70f82502016-01-29 00:32:36 -0800791 }
Frank Tang1f164ee2022-11-08 12:31:27 -0800792 return true;
Jungshik Shin70f82502016-01-29 00:32:36 -0800793}
794
795static UBool // returns U_SUCCESS(*status)
796insertRootBundle(UResourceDataEntry *&t1, UErrorCode *status) {
Frank Tang1f164ee2022-11-08 12:31:27 -0800797 if (U_FAILURE(*status)) { return false; }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000798 UErrorCode parentStatus = U_ZERO_ERROR;
Jungshik Shin70f82502016-01-29 00:32:36 -0800799 UResourceDataEntry *t2 = init_entry(kRootLocaleName, t1->fPath, &parentStatus);
800 if (U_FAILURE(parentStatus)) {
801 *status = parentStatus;
Frank Tang1f164ee2022-11-08 12:31:27 -0800802 return false;
Jungshik Shin70f82502016-01-29 00:32:36 -0800803 }
804 t1->fParent = t2;
805 t1 = t2;
Frank Tang1f164ee2022-11-08 12:31:27 -0800806 return true;
Jungshik Shin70f82502016-01-29 00:32:36 -0800807}
808
Jungshik Shin70f82502016-01-29 00:32:36 -0800809static UResourceDataEntry *entryOpen(const char* path, const char* localeID,
810 UResOpenType openType, UErrorCode* status) {
811 U_ASSERT(openType != URES_OPEN_DIRECT);
812 UErrorCode intStatus = U_ZERO_ERROR;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000813 UResourceDataEntry *r = NULL;
814 UResourceDataEntry *t1 = NULL;
Frank Tang1f164ee2022-11-08 12:31:27 -0800815 UBool isDefault = false;
816 UBool isRoot = false;
817 UBool hasRealData = false;
818 UBool hasChopped = true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000819 UBool usingUSRData = U_USE_USRDATA && ( path == NULL || uprv_strncmp(path,U_ICUDATA_NAME,8) == 0);
820
821 char name[ULOC_FULLNAME_CAPACITY];
822 char usrDataPath[96];
823
824 initCache(status);
825
826 if(U_FAILURE(*status)) {
827 return NULL;
828 }
829
830 uprv_strncpy(name, localeID, sizeof(name) - 1);
831 name[sizeof(name) - 1] = 0;
832
833 if ( usingUSRData ) {
834 if ( path == NULL ) {
835 uprv_strcpy(usrDataPath, U_USRDATA_NAME);
836 } else {
837 uprv_strncpy(usrDataPath, path, sizeof(usrDataPath) - 1);
838 usrDataPath[0] = 'u';
839 usrDataPath[1] = 's';
840 usrDataPath[2] = 'r';
841 usrDataPath[sizeof(usrDataPath) - 1] = 0;
842 }
843 }
844
Frank Tang3e05d9d2021-11-08 14:04:04 -0800845 // Note: We need to query the default locale *before* locking resbMutex.
846 const char *defaultLocale = uloc_getDefault();
847
Frank Tangb8696612019-10-25 14:58:21 -0700848 Mutex lock(&resbMutex); // Lock resbMutex until the end of this function.
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000849
Frank Tangb8696612019-10-25 14:58:21 -0700850 /* We're going to skip all the locales that do not have any data */
Frank Tang1f164ee2022-11-08 12:31:27 -0800851 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
Frank Tangb8696612019-10-25 14:58:21 -0700852
853 // If we failed due to out-of-memory, report the failure and exit early.
854 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
855 *status = intStatus;
856 goto finish;
857 }
858
859 if(r != NULL) { /* if there is one real locale, we can look for parents. */
860 t1 = r;
Frank Tang1f164ee2022-11-08 12:31:27 -0800861 hasRealData = true;
Frank Tangb8696612019-10-25 14:58:21 -0700862 if ( usingUSRData ) { /* This code inserts user override data into the inheritance chain */
863 UErrorCode usrStatus = U_ZERO_ERROR;
864 UResourceDataEntry *u1 = init_entry(t1->fName, usrDataPath, &usrStatus);
865 // If we failed due to out-of-memory, report the failure and exit early.
866 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
867 *status = intStatus;
868 goto finish;
869 }
870 if ( u1 != NULL ) {
871 if(u1->fBogus == U_ZERO_ERROR) {
872 u1->fParent = t1;
873 r = u1;
874 } else {
875 /* the USR override data wasn't found, set it to be deleted */
876 u1->fCountExisting = 0;
877 }
878 }
879 }
Frank Tang7e7574b2021-04-13 21:19:13 -0700880 if ((hasChopped || mayHaveParent(name)) && !isRoot) {
Frank Tangb8696612019-10-25 14:58:21 -0700881 if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
882 goto finish;
883 }
884 }
885 }
886
887 /* we could have reached this point without having any real data */
888 /* if that is the case, we need to chain in the default locale */
889 if(r==NULL && openType == URES_OPEN_LOCALE_DEFAULT_ROOT && !isDefault && !isRoot) {
890 /* insert default locale */
Frank Tang3e05d9d2021-11-08 14:04:04 -0800891 uprv_strcpy(name, defaultLocale);
Frank Tang1f164ee2022-11-08 12:31:27 -0800892 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
Jungshik Shin42d50272018-10-24 01:22:09 -0700893 // If we failed due to out-of-memory, report the failure and exit early.
894 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
895 *status = intStatus;
Frank Tangb8696612019-10-25 14:58:21 -0700896 goto finish;
Jungshik Shin42d50272018-10-24 01:22:09 -0700897 }
Frank Tangb8696612019-10-25 14:58:21 -0700898 intStatus = U_USING_DEFAULT_WARNING;
899 if(r != NULL) { /* the default locale exists */
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000900 t1 = r;
Frank Tang1f164ee2022-11-08 12:31:27 -0800901 hasRealData = true;
902 isDefault = true;
Frank Tangb8696612019-10-25 14:58:21 -0700903 // TODO: Why not if (usingUSRData) { ... } like in the non-default-locale code path?
Frank Tang7e7574b2021-04-13 21:19:13 -0700904 if ((hasChopped || mayHaveParent(name)) && !isRoot) {
Jungshik Shin70f82502016-01-29 00:32:36 -0800905 if (!loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), usingUSRData, usrDataPath, status)) {
Frank Tangb8696612019-10-25 14:58:21 -0700906 goto finish;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000907 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000908 }
909 }
Frank Tangb8696612019-10-25 14:58:21 -0700910 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000911
Frank Tangb8696612019-10-25 14:58:21 -0700912 /* we could still have r == NULL at this point - maybe even default locale is not */
913 /* present */
914 if(r == NULL) {
915 uprv_strcpy(name, kRootLocaleName);
Frank Tang1f164ee2022-11-08 12:31:27 -0800916 r = findFirstExisting(path, name, defaultLocale, openType, &isRoot, &hasChopped, &isDefault, &intStatus);
Frank Tangb8696612019-10-25 14:58:21 -0700917 // If we failed due to out-of-memory, report the failure and exit early.
918 if (intStatus == U_MEMORY_ALLOCATION_ERROR) {
919 *status = intStatus;
920 goto finish;
921 }
922 if(r != NULL) {
923 t1 = r;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000924 intStatus = U_USING_DEFAULT_WARNING;
Frank Tang1f164ee2022-11-08 12:31:27 -0800925 hasRealData = true;
Frank Tangb8696612019-10-25 14:58:21 -0700926 } else { /* we don't even have the root locale */
927 *status = U_MISSING_RESOURCE_ERROR;
928 goto finish;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000929 }
Frank Tangb8696612019-10-25 14:58:21 -0700930 } else if(!isRoot && uprv_strcmp(t1->fName, kRootLocaleName) != 0 &&
931 t1->fParent == NULL && !r->fData.noFallback) {
932 if (!insertRootBundle(t1, status)) {
933 goto finish;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000934 }
Frank Tangb8696612019-10-25 14:58:21 -0700935 if(!hasRealData) {
936 r->fBogus = U_USING_DEFAULT_WARNING;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000937 }
Frank Tangb8696612019-10-25 14:58:21 -0700938 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000939
Frank Tangb8696612019-10-25 14:58:21 -0700940 // TODO: Does this ever loop?
941 while(r != NULL && !isRoot && t1->fParent != NULL) {
942 t1->fParent->fCountExisting++;
943 t1 = t1->fParent;
944 }
945
946finish:
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000947 if(U_SUCCESS(*status)) {
Jungshik Shin70f82502016-01-29 00:32:36 -0800948 if(intStatus != U_ZERO_ERROR) {
949 *status = intStatus;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000950 }
Jungshik Shin70f82502016-01-29 00:32:36 -0800951 return r;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +0000952 } else {
953 return NULL;
954 }
955}
956
Jungshik Shin70f82502016-01-29 00:32:36 -0800957/**
958 * Version of entryOpen() and findFirstExisting() for ures_openDirect(),
959 * with no fallbacks.
960 * Parent and root locale bundles are loaded if
961 * the requested bundle does not have the "nofallback" flag.
962 */
963static UResourceDataEntry *
964entryOpenDirect(const char* path, const char* localeID, UErrorCode* status) {
965 initCache(status);
966 if(U_FAILURE(*status)) {
967 return NULL;
968 }
969
Frank Tang3e05d9d2021-11-08 14:04:04 -0800970 // Note: We need to query the default locale *before* locking resbMutex.
971 // If the localeID is NULL, then we want to use the default locale.
972 if (localeID == NULL) {
973 localeID = uloc_getDefault();
974 } else if (*localeID == 0) {
975 // If the localeID is "", then we want to use the root locale.
976 localeID = kRootLocaleName;
977 }
978
Frank Tangb8696612019-10-25 14:58:21 -0700979 Mutex lock(&resbMutex);
Frank Tang3e05d9d2021-11-08 14:04:04 -0800980
Jungshik Shin70f82502016-01-29 00:32:36 -0800981 // findFirstExisting() without fallbacks.
982 UResourceDataEntry *r = init_entry(localeID, path, status);
983 if(U_SUCCESS(*status)) {
984 if(r->fBogus != U_ZERO_ERROR) {
985 r->fCountExisting--;
986 r = NULL;
987 }
988 } else {
989 r = NULL;
990 }
991
992 // Some code depends on the ures_openDirect() bundle to have a parent bundle chain,
993 // unless it is marked with "nofallback".
994 UResourceDataEntry *t1 = r;
995 if(r != NULL && uprv_strcmp(localeID, kRootLocaleName) != 0 && // not root
996 r->fParent == NULL && !r->fData.noFallback &&
997 uprv_strlen(localeID) < ULOC_FULLNAME_CAPACITY) {
998 char name[ULOC_FULLNAME_CAPACITY];
999 uprv_strcpy(name, localeID);
1000 if(!chopLocale(name) || uprv_strcmp(name, kRootLocaleName) == 0 ||
Frank Tang1f164ee2022-11-08 12:31:27 -08001001 loadParentsExceptRoot(t1, name, UPRV_LENGTHOF(name), false, NULL, status)) {
Jungshik Shin70f82502016-01-29 00:32:36 -08001002 if(uprv_strcmp(t1->fName, kRootLocaleName) != 0 && t1->fParent == NULL) {
1003 insertRootBundle(t1, status);
1004 }
1005 }
1006 if(U_FAILURE(*status)) {
1007 r = NULL;
1008 }
1009 }
1010
1011 if(r != NULL) {
1012 // TODO: Does this ever loop?
1013 while(t1->fParent != NULL) {
1014 t1->fParent->fCountExisting++;
1015 t1 = t1->fParent;
1016 }
1017 }
Jungshik Shin70f82502016-01-29 00:32:36 -08001018 return r;
1019}
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001020
1021/**
1022 * Functions to create and destroy resource bundles.
1023 * CAUTION: resbMutex must be locked when calling this function.
1024 */
1025/* INTERNAL: */
1026static void entryCloseInt(UResourceDataEntry *resB) {
1027 UResourceDataEntry *p = resB;
1028
1029 while(resB != NULL) {
1030 p = resB->fParent;
1031 resB->fCountExisting--;
1032
1033 /* Entries are left in the cache. TODO: add ures_flushCache() to force a flush
1034 of the cache. */
1035/*
1036 if(resB->fCountExisting <= 0) {
1037 uhash_remove(cache, resB);
1038 if(resB->fBogus == U_ZERO_ERROR) {
1039 res_unload(&(resB->fData));
1040 }
1041 if(resB->fName != NULL) {
1042 uprv_free(resB->fName);
1043 }
1044 if(resB->fPath != NULL) {
1045 uprv_free(resB->fPath);
1046 }
1047 uprv_free(resB);
1048 }
1049*/
1050
1051 resB = p;
1052 }
1053}
1054
1055/**
1056 * API: closes a resource bundle and cleans up.
1057 */
1058
1059static void entryClose(UResourceDataEntry *resB) {
Frank Tangb8696612019-10-25 14:58:21 -07001060 Mutex lock(&resbMutex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001061 entryCloseInt(resB);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001062}
1063
1064/*
1065U_CFUNC void ures_setResPath(UResourceBundle *resB, const char* toAdd) {
1066 if(resB->fResPath == NULL) {
1067 resB->fResPath = resB->fResBuf;
1068 *(resB->fResPath) = 0;
1069 }
1070 resB->fResPathLen = uprv_strlen(toAdd);
1071 if(RES_BUFSIZE <= resB->fResPathLen+1) {
1072 if(resB->fResPath == resB->fResBuf) {
1073 resB->fResPath = (char *)uprv_malloc((resB->fResPathLen+1)*sizeof(char));
1074 } else {
1075 resB->fResPath = (char *)uprv_realloc(resB->fResPath, (resB->fResPathLen+1)*sizeof(char));
1076 }
1077 }
1078 uprv_strcpy(resB->fResPath, toAdd);
1079}
1080*/
1081static void ures_appendResPath(UResourceBundle *resB, const char* toAdd, int32_t lenToAdd, UErrorCode *status) {
1082 int32_t resPathLenOrig = resB->fResPathLen;
1083 if(resB->fResPath == NULL) {
1084 resB->fResPath = resB->fResBuf;
1085 *(resB->fResPath) = 0;
1086 resB->fResPathLen = 0;
1087 }
1088 resB->fResPathLen += lenToAdd;
1089 if(RES_BUFSIZE <= resB->fResPathLen+1) {
1090 if(resB->fResPath == resB->fResBuf) {
1091 resB->fResPath = (char *)uprv_malloc((resB->fResPathLen+1)*sizeof(char));
1092 /* Check that memory was allocated correctly. */
1093 if (resB->fResPath == NULL) {
1094 *status = U_MEMORY_ALLOCATION_ERROR;
1095 return;
1096 }
1097 uprv_strcpy(resB->fResPath, resB->fResBuf);
1098 } else {
1099 char *temp = (char *)uprv_realloc(resB->fResPath, (resB->fResPathLen+1)*sizeof(char));
1100 /* Check that memory was reallocated correctly. */
1101 if (temp == NULL) {
1102 *status = U_MEMORY_ALLOCATION_ERROR;
1103 return;
1104 }
1105 resB->fResPath = temp;
1106 }
1107 }
1108 uprv_strcpy(resB->fResPath + resPathLenOrig, toAdd);
1109}
1110
1111static void ures_freeResPath(UResourceBundle *resB) {
1112 if (resB->fResPath && resB->fResPath != resB->fResBuf) {
1113 uprv_free(resB->fResPath);
1114 }
1115 resB->fResPath = NULL;
1116 resB->fResPathLen = 0;
1117}
1118
1119static void
1120ures_closeBundle(UResourceBundle* resB, UBool freeBundleObj)
1121{
1122 if(resB != NULL) {
1123 if(resB->fData != NULL) {
1124 entryClose(resB->fData);
1125 }
1126 if(resB->fVersion != NULL) {
1127 uprv_free(resB->fVersion);
1128 }
1129 ures_freeResPath(resB);
1130
Frank Tang1f164ee2022-11-08 12:31:27 -08001131 if(ures_isStackObject(resB) == false && freeBundleObj) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001132 uprv_free(resB);
1133 }
1134#if 0 /*U_DEBUG*/
1135 else {
1136 /* poison the data */
1137 uprv_memset(resB, -1, sizeof(UResourceBundle));
1138 }
1139#endif
1140 }
1141}
1142
1143U_CAPI void U_EXPORT2
1144ures_close(UResourceBundle* resB)
1145{
Frank Tang1f164ee2022-11-08 12:31:27 -08001146 ures_closeBundle(resB, true);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001147}
1148
Frank Tang3e05d9d2021-11-08 14:04:04 -08001149namespace {
1150
1151UResourceBundle *init_resb_result(
1152 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1153 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1154 int32_t recursionDepth,
1155 UResourceBundle *resB, UErrorCode *status);
1156
1157// TODO: Try to refactor further, so that we output a dataEntry + Resource + (optionally) resPath,
1158// rather than a UResourceBundle.
1159// May need to entryIncrease() the resulting dataEntry.
1160UResourceBundle *getAliasTargetAsResourceBundle(
1161 const ResourceData &resData, Resource r, const char *key, int32_t idx,
1162 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1163 int32_t recursionDepth,
1164 UResourceBundle *resB, UErrorCode *status) {
1165 // TODO: When an error occurs: Should we return nullptr vs. resB?
1166 if (U_FAILURE(*status)) { return resB; }
1167 U_ASSERT(RES_GET_TYPE(r) == URES_ALIAS);
1168 int32_t len = 0;
1169 const UChar *alias = res_getAlias(&resData, r, &len);
1170 if(len <= 0) {
1171 // bad alias
1172 *status = U_ILLEGAL_ARGUMENT_ERROR;
1173 return resB;
1174 }
1175
1176 // Copy the UTF-16 alias string into an invariant-character string.
1177 //
1178 // We do this so that res_findResource() can modify the path,
1179 // which allows us to remove redundant _res_findResource() variants
1180 // in uresdata.c.
1181 // res_findResource() now NUL-terminates each segment so that table keys
1182 // can always be compared with strcmp() instead of strncmp().
1183 // Saves code there and simplifies testing and code coverage.
1184 //
1185 // markus 2003oct17
1186 CharString chAlias;
1187 chAlias.appendInvariantChars(alias, len, *status);
1188 if (U_FAILURE(*status)) {
1189 return nullptr;
1190 }
1191
1192 // We have an alias, now let's cut it up.
1193 const char *path = nullptr, *locale = nullptr, *keyPath = nullptr;
1194 if(chAlias[0] == RES_PATH_SEPARATOR) {
1195 // There is a path included.
1196 char *chAliasData = chAlias.data();
1197 char *sep = chAliasData + 1;
1198 path = sep;
1199 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1200 if(sep != nullptr) {
1201 *sep++ = 0;
1202 }
1203 if(uprv_strcmp(path, "LOCALE") == 0) {
1204 // This is an XPath alias, starting with "/LOCALE/".
1205 // It contains the path to a resource which should be looked up
1206 // starting in the valid locale.
1207 // TODO: Can/should we forbid a /LOCALE alias without key path?
1208 // It seems weird to alias to the same path, just starting from the valid locale.
1209 // That will often yield an infinite loop.
1210 keyPath = sep;
1211 // Read from the valid locale which we already have.
1212 path = locale = nullptr;
1213 } else {
1214 if(uprv_strcmp(path, "ICUDATA") == 0) { /* want ICU data */
1215 path = nullptr;
1216 }
1217 if (sep == nullptr) {
1218 // TODO: This ends up using the root bundle. Can/should we forbid this?
1219 locale = "";
1220 } else {
1221 locale = sep;
1222 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1223 if(sep != nullptr) {
1224 *sep++ = 0;
1225 }
1226 keyPath = sep;
1227 }
1228 }
1229 } else {
1230 // No path, start with a locale.
1231 char *sep = chAlias.data();
1232 locale = sep;
1233 sep = uprv_strchr(sep, RES_PATH_SEPARATOR);
1234 if(sep != nullptr) {
1235 *sep++ = 0;
1236 }
1237 keyPath = sep;
1238 path = validLocaleDataEntry->fPath;
1239 }
1240
1241 // Got almost everything, let's try to open.
1242 // First, open the bundle with real data.
1243 LocalUResourceBundlePointer mainRes;
1244 UResourceDataEntry *dataEntry;
1245 if (locale == nullptr) {
1246 // alias = /LOCALE/keyPath
1247 // Read from the valid locale which we already have.
1248 dataEntry = validLocaleDataEntry;
1249 } else {
1250 UErrorCode intStatus = U_ZERO_ERROR;
1251 // TODO: Shouldn't we use ures_open() for locale data bundles (!noFallback)?
1252 mainRes.adoptInstead(ures_openDirect(path, locale, &intStatus));
1253 if(U_FAILURE(intStatus)) {
1254 // We failed to open the resource bundle we're aliasing to.
1255 *status = intStatus;
1256 return resB;
1257 }
1258 dataEntry = mainRes->fData;
1259 }
1260
1261 const char* temp = nullptr;
1262 if(keyPath == nullptr) {
1263 // No key path. This means that we are going to to use the corresponding resource from
1264 // another bundle.
1265 // TODO: Why the special code path?
1266 // Why not put together a key path from containerResPath + key or idx,
1267 // as a comment below suggests, and go into the regular code branch?
1268 // First, we are going to get a corresponding container
1269 // resource to the one we are searching.
1270 r = dataEntry->fData.rootRes;
1271 if(containerResPath) {
1272 chAlias.clear().append(containerResPath, *status);
1273 if (U_FAILURE(*status)) {
1274 return nullptr;
1275 }
1276 char *aKey = chAlias.data();
1277 // TODO: should res_findResource() return a new dataEntry, too?
1278 r = res_findResource(&dataEntry->fData, r, &aKey, &temp);
1279 }
1280 if(key) {
1281 // We need to make keyPath from the containerResPath and
1282 // current key, if there is a key associated.
1283 chAlias.clear().append(key, *status);
1284 if (U_FAILURE(*status)) {
1285 return nullptr;
1286 }
1287 char *aKey = chAlias.data();
1288 r = res_findResource(&dataEntry->fData, r, &aKey, &temp);
1289 } else if(idx != -1) {
1290 // If there is no key, but there is an index, try to get by the index.
1291 // Here we have either a table or an array, so get the element.
1292 int32_t type = RES_GET_TYPE(r);
1293 if(URES_IS_TABLE(type)) {
1294 const char *aKey;
1295 r = res_getTableItemByIndex(&dataEntry->fData, r, idx, &aKey);
1296 } else { /* array */
1297 r = res_getArrayItem(&dataEntry->fData, r, idx);
1298 }
1299 }
1300 if(r != RES_BOGUS) {
1301 resB = init_resb_result(
1302 dataEntry, r, temp, -1, validLocaleDataEntry, nullptr, recursionDepth+1,
1303 resB, status);
1304 } else {
1305 *status = U_MISSING_RESOURCE_ERROR;
1306 }
1307 } else {
1308 // This one is a bit trickier.
1309 // We start finding keys, but after we resolve one alias, the path might continue.
1310 // Consider:
1311 // aliastest:alias { "testtypes/anotheralias/Sequence" }
1312 // anotheralias:alias { "/ICUDATA/sh/CollationElements" }
1313 // aliastest resource should finally have the sequence, not collation elements.
1314 CharString pathBuf(keyPath, *status);
1315 if (U_FAILURE(*status)) {
1316 return nullptr;
1317 }
1318 char *myPath = pathBuf.data();
1319 containerResPath = nullptr;
1320 // Now we have fallback following here.
1321 for(;;) {
1322 r = dataEntry->fData.rootRes;
1323 // TODO: Move containerResPath = nullptr to here,
1324 // consistent with restarting from the rootRes of another bundle?!
1325
1326 // This loop handles 'found' resources over several levels.
1327 while(*myPath && U_SUCCESS(*status)) {
1328 r = res_findResource(&(dataEntry->fData), r, &myPath, &temp);
1329 if(r == RES_BOGUS) {
1330 // No resource found, we don't really want to look anymore on this level.
1331 break;
1332 }
1333 // Found a resource, but it might be an indirection.
1334 resB = init_resb_result(
1335 dataEntry, r, temp, -1,
1336 validLocaleDataEntry, containerResPath, recursionDepth+1,
1337 resB, status);
1338 if (U_FAILURE(*status)) {
1339 break;
1340 }
1341 if (temp == nullptr || uprv_strcmp(keyPath, temp) != 0) {
1342 // The call to init_resb_result() above will set resB->fKeyPath to be
1343 // the same as resB->fKey,
1344 // throwing away any additional path elements if we had them --
1345 // if the key path wasn't just a single resource ID, clear out
1346 // the bundle's key path and re-set it to be equal to keyPath.
1347 ures_freeResPath(resB);
1348 ures_appendResPath(resB, keyPath, (int32_t)uprv_strlen(keyPath), status);
1349 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1350 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1351 }
1352 if (U_FAILURE(*status)) {
1353 break;
1354 }
1355 }
1356 r = resB->fRes; /* switch to a new resource, possibly a new tree */
1357 dataEntry = resB->fData;
1358 containerResPath = resB->fResPath;
1359 }
1360 if (U_FAILURE(*status) || r != RES_BOGUS) {
1361 break;
1362 }
1363 // Fall back to the parent bundle, if there is one.
1364 dataEntry = dataEntry->fParent;
1365 if (dataEntry == nullptr) {
1366 *status = U_MISSING_RESOURCE_ERROR;
1367 break;
1368 }
1369 // Copy the same keyPath again.
1370 myPath = pathBuf.data();
1371 uprv_strcpy(myPath, keyPath);
1372 }
1373 }
1374 if(mainRes.getAlias() == resB) {
1375 mainRes.orphan();
1376 }
1377 ResourceTracer(resB).maybeTrace("getalias");
1378 return resB;
1379}
1380
1381// Recursive function, should be called only by itself, by its simpler wrapper,
1382// or by getAliasTargetAsResourceBundle().
1383UResourceBundle *init_resb_result(
1384 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1385 UResourceDataEntry *validLocaleDataEntry, const char *containerResPath,
1386 int32_t recursionDepth,
1387 UResourceBundle *resB, UErrorCode *status) {
1388 // TODO: When an error occurs: Should we return nullptr vs. resB?
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001389 if(status == NULL || U_FAILURE(*status)) {
1390 return resB;
1391 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001392 if (validLocaleDataEntry == nullptr) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001393 *status = U_ILLEGAL_ARGUMENT_ERROR;
1394 return NULL;
1395 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001396 if(RES_GET_TYPE(r) == URES_ALIAS) {
1397 // This is an alias, need to exchange with real data.
1398 if(recursionDepth >= URES_MAX_ALIAS_LEVEL) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001399 *status = U_TOO_MANY_ALIASES_ERROR;
1400 return resB;
1401 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001402 return getAliasTargetAsResourceBundle(
1403 dataEntry->fData, r, key, idx,
1404 validLocaleDataEntry, containerResPath, recursionDepth, resB, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001405 }
1406 if(resB == NULL) {
1407 resB = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001408 if (resB == NULL) {
1409 *status = U_MEMORY_ALLOCATION_ERROR;
1410 return NULL;
1411 }
Frank Tang1f164ee2022-11-08 12:31:27 -08001412 ures_setIsStackObject(resB, false);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001413 resB->fResPath = NULL;
1414 resB->fResPathLen = 0;
1415 } else {
1416 if(resB->fData != NULL) {
1417 entryClose(resB->fData);
1418 }
1419 if(resB->fVersion != NULL) {
1420 uprv_free(resB->fVersion);
1421 }
1422 /*
1423 weiv: if stack object was passed in, it doesn't really need to be reinited,
1424 since the purpose of initing is to remove stack junk. However, at this point
1425 we would not do anything to an allocated object, so stack object should be
1426 treated the same
1427 */
1428 /*
Frank Tang1f164ee2022-11-08 12:31:27 -08001429 if(ures_isStackObject(resB) != false) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001430 ures_initStackObject(resB);
1431 }
1432 */
Frank Tang3e05d9d2021-11-08 14:04:04 -08001433 if(containerResPath != resB->fResPath) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001434 ures_freeResPath(resB);
1435 }
1436 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001437 resB->fData = dataEntry;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001438 entryIncrease(resB->fData);
Frank Tang1f164ee2022-11-08 12:31:27 -08001439 resB->fHasFallback = false;
1440 resB->fIsTopLevel = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001441 resB->fIndex = -1;
1442 resB->fKey = key;
Frank Tang3e05d9d2021-11-08 14:04:04 -08001443 resB->fValidLocaleDataEntry = validLocaleDataEntry;
1444 if(containerResPath != resB->fResPath) {
1445 ures_appendResPath(
1446 resB, containerResPath, static_cast<int32_t>(uprv_strlen(containerResPath)), status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001447 }
1448 if(key != NULL) {
1449 ures_appendResPath(resB, key, (int32_t)uprv_strlen(key), status);
1450 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1451 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1452 }
1453 } else if(idx >= 0) {
1454 char buf[256];
1455 int32_t len = T_CString_integerToString(buf, idx, 10);
1456 ures_appendResPath(resB, buf, len, status);
1457 if(resB->fResPath[resB->fResPathLen-1] != RES_PATH_SEPARATOR) {
1458 ures_appendResPath(resB, RES_PATH_SEPARATOR_S, 1, status);
1459 }
1460 }
1461 /* Make sure that Purify doesn't complain about uninitialized memory copies. */
1462 {
1463 int32_t usedLen = ((resB->fResBuf == resB->fResPath) ? resB->fResPathLen : 0);
1464 uprv_memset(resB->fResBuf + usedLen, 0, sizeof(resB->fResBuf) - usedLen);
1465 }
1466
1467 resB->fVersion = NULL;
1468 resB->fRes = r;
Frank Tang3e05d9d2021-11-08 14:04:04 -08001469 resB->fSize = res_countArrayItems(&resB->getResData(), resB->fRes);
Frank Tangb8696612019-10-25 14:58:21 -07001470 ResourceTracer(resB).trace("get");
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001471 return resB;
1472}
1473
Frank Tang3e05d9d2021-11-08 14:04:04 -08001474UResourceBundle *init_resb_result(
1475 UResourceDataEntry *dataEntry, Resource r, const char *key, int32_t idx,
1476 // validLocaleDataEntry + containerResPath
1477 const UResourceBundle *container,
1478 UResourceBundle *resB, UErrorCode *status) {
1479 return init_resb_result(
1480 dataEntry, r, key, idx,
1481 container->fValidLocaleDataEntry, container->fResPath, 0, resB, status);
1482}
1483
1484} // namespace
1485
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001486UResourceBundle *ures_copyResb(UResourceBundle *r, const UResourceBundle *original, UErrorCode *status) {
1487 UBool isStackObject;
1488 if(U_FAILURE(*status) || r == original) {
1489 return r;
1490 }
1491 if(original != NULL) {
1492 if(r == NULL) {
Frank Tang1f164ee2022-11-08 12:31:27 -08001493 isStackObject = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001494 r = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
1495 /* test for NULL */
1496 if (r == NULL) {
1497 *status = U_MEMORY_ALLOCATION_ERROR;
1498 return NULL;
1499 }
1500 } else {
1501 isStackObject = ures_isStackObject(r);
Frank Tang1f164ee2022-11-08 12:31:27 -08001502 ures_closeBundle(r, false);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001503 }
1504 uprv_memcpy(r, original, sizeof(UResourceBundle));
1505 r->fResPath = NULL;
1506 r->fResPathLen = 0;
1507 if(original->fResPath) {
1508 ures_appendResPath(r, original->fResPath, original->fResPathLen, status);
1509 }
1510 ures_setIsStackObject(r, isStackObject);
1511 if(r->fData != NULL) {
1512 entryIncrease(r->fData);
1513 }
1514 }
1515 return r;
1516}
1517
1518/**
1519 * Functions to retrieve data from resource bundles.
1520 */
1521
1522U_CAPI const UChar* U_EXPORT2 ures_getString(const UResourceBundle* resB, int32_t* len, UErrorCode* status) {
1523 const UChar *s;
1524 if (status==NULL || U_FAILURE(*status)) {
1525 return NULL;
1526 }
1527 if(resB == NULL) {
1528 *status = U_ILLEGAL_ARGUMENT_ERROR;
1529 return NULL;
1530 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001531 s = res_getString({resB}, &resB->getResData(), resB->fRes, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001532 if (s == NULL) {
1533 *status = U_RESOURCE_TYPE_MISMATCH;
1534 }
1535 return s;
1536}
1537
1538static const char *
1539ures_toUTF8String(const UChar *s16, int32_t length16,
1540 char *dest, int32_t *pLength,
1541 UBool forceCopy,
1542 UErrorCode *status) {
1543 int32_t capacity;
1544
1545 if (U_FAILURE(*status)) {
1546 return NULL;
1547 }
1548 if (pLength != NULL) {
1549 capacity = *pLength;
1550 } else {
1551 capacity = 0;
1552 }
1553 if (capacity < 0 || (capacity > 0 && dest == NULL)) {
1554 *status = U_ILLEGAL_ARGUMENT_ERROR;
1555 return NULL;
1556 }
1557
1558 if (length16 == 0) {
1559 /* empty string, return as read-only pointer */
1560 if (pLength != NULL) {
1561 *pLength = 0;
1562 }
1563 if (forceCopy) {
1564 u_terminateChars(dest, capacity, 0, status);
1565 return dest;
1566 } else {
1567 return "";
1568 }
1569 } else {
1570 /* We need to transform the string to the destination buffer. */
1571 if (capacity < length16) {
1572 /* No chance for the string to fit. Pure preflighting. */
1573 return u_strToUTF8(NULL, 0, pLength, s16, length16, status);
1574 }
1575 if (!forceCopy && (length16 <= 0x2aaaaaaa)) {
1576 /*
1577 * We know the string will fit into dest because each UChar turns
1578 * into at most three UTF-8 bytes. Fill the latter part of dest
1579 * so that callers do not expect to use dest as a string pointer,
1580 * hopefully leading to more robust code for when resource bundles
1581 * may store UTF-8 natively.
1582 * (In which case dest would not be used at all.)
1583 *
Frank Tang1f164ee2022-11-08 12:31:27 -08001584 * We do not do this if forceCopy=true because then the caller
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001585 * expects the string to start exactly at dest.
1586 *
1587 * The test above for <= 0x2aaaaaaa prevents overflows.
1588 * The +1 is for the NUL terminator.
1589 */
1590 int32_t maxLength = 3 * length16 + 1;
1591 if (capacity > maxLength) {
1592 dest += capacity - maxLength;
1593 capacity = maxLength;
1594 }
1595 }
1596 return u_strToUTF8(dest, capacity, pLength, s16, length16, status);
1597 }
1598}
1599
1600U_CAPI const char * U_EXPORT2
1601ures_getUTF8String(const UResourceBundle *resB,
1602 char *dest, int32_t *pLength,
1603 UBool forceCopy,
1604 UErrorCode *status) {
1605 int32_t length16;
1606 const UChar *s16 = ures_getString(resB, &length16, status);
1607 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
1608}
1609
1610U_CAPI const uint8_t* U_EXPORT2 ures_getBinary(const UResourceBundle* resB, int32_t* len,
1611 UErrorCode* status) {
1612 const uint8_t *p;
1613 if (status==NULL || U_FAILURE(*status)) {
1614 return NULL;
1615 }
1616 if(resB == NULL) {
1617 *status = U_ILLEGAL_ARGUMENT_ERROR;
1618 return NULL;
1619 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001620 p = res_getBinary({resB}, &resB->getResData(), resB->fRes, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001621 if (p == NULL) {
1622 *status = U_RESOURCE_TYPE_MISMATCH;
1623 }
1624 return p;
1625}
1626
1627U_CAPI const int32_t* U_EXPORT2 ures_getIntVector(const UResourceBundle* resB, int32_t* len,
1628 UErrorCode* status) {
1629 const int32_t *p;
1630 if (status==NULL || U_FAILURE(*status)) {
1631 return NULL;
1632 }
1633 if(resB == NULL) {
1634 *status = U_ILLEGAL_ARGUMENT_ERROR;
1635 return NULL;
1636 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001637 p = res_getIntVector({resB}, &resB->getResData(), resB->fRes, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001638 if (p == NULL) {
1639 *status = U_RESOURCE_TYPE_MISMATCH;
1640 }
1641 return p;
1642}
1643
1644/* this function returns a signed integer */
1645/* it performs sign extension */
1646U_CAPI int32_t U_EXPORT2 ures_getInt(const UResourceBundle* resB, UErrorCode *status) {
1647 if (status==NULL || U_FAILURE(*status)) {
1648 return 0xffffffff;
1649 }
1650 if(resB == NULL) {
1651 *status = U_ILLEGAL_ARGUMENT_ERROR;
1652 return 0xffffffff;
1653 }
1654 if(RES_GET_TYPE(resB->fRes) != URES_INT) {
1655 *status = U_RESOURCE_TYPE_MISMATCH;
1656 return 0xffffffff;
1657 }
Frank Tang952ccb92019-08-22 12:09:17 -07001658 return res_getInt({resB}, resB->fRes);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001659}
1660
1661U_CAPI uint32_t U_EXPORT2 ures_getUInt(const UResourceBundle* resB, UErrorCode *status) {
1662 if (status==NULL || U_FAILURE(*status)) {
1663 return 0xffffffff;
1664 }
1665 if(resB == NULL) {
1666 *status = U_ILLEGAL_ARGUMENT_ERROR;
1667 return 0xffffffff;
1668 }
1669 if(RES_GET_TYPE(resB->fRes) != URES_INT) {
1670 *status = U_RESOURCE_TYPE_MISMATCH;
1671 return 0xffffffff;
1672 }
Frank Tang952ccb92019-08-22 12:09:17 -07001673 return res_getUInt({resB}, resB->fRes);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001674}
1675
1676U_CAPI UResType U_EXPORT2 ures_getType(const UResourceBundle *resB) {
1677 if(resB == NULL) {
1678 return URES_NONE;
1679 }
1680 return res_getPublicType(resB->fRes);
1681}
1682
1683U_CAPI const char * U_EXPORT2 ures_getKey(const UResourceBundle *resB) {
Frank Tang952ccb92019-08-22 12:09:17 -07001684 //
1685 // TODO: Trace ures_getKey? I guess not usually.
1686 //
1687 // We usually get the key string to decide whether we want the value, or to
1688 // make a key-value pair. Tracing the value should suffice.
1689 //
1690 // However, I believe we have some data (e.g., in res_index) where the key
1691 // strings are the data. Tracing the enclosing table should suffice.
1692 //
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001693 if(resB == NULL) {
1694 return NULL;
1695 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001696 return(resB->fKey);
1697}
1698
1699U_CAPI int32_t U_EXPORT2 ures_getSize(const UResourceBundle *resB) {
1700 if(resB == NULL) {
1701 return 0;
1702 }
1703
1704 return resB->fSize;
1705}
1706
1707static const UChar* ures_getStringWithAlias(const UResourceBundle *resB, Resource r, int32_t sIndex, int32_t *len, UErrorCode *status) {
1708 if(RES_GET_TYPE(r) == URES_ALIAS) {
1709 const UChar* result = 0;
1710 UResourceBundle *tempRes = ures_getByIndex(resB, sIndex, NULL, status);
1711 result = ures_getString(tempRes, len, status);
1712 ures_close(tempRes);
1713 return result;
1714 } else {
Frank Tang3e05d9d2021-11-08 14:04:04 -08001715 return res_getString({resB, sIndex}, &resB->getResData(), r, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001716 }
1717}
1718
1719U_CAPI void U_EXPORT2 ures_resetIterator(UResourceBundle *resB){
1720 if(resB == NULL) {
1721 return;
1722 }
1723 resB->fIndex = -1;
1724}
1725
1726U_CAPI UBool U_EXPORT2 ures_hasNext(const UResourceBundle *resB) {
1727 if(resB == NULL) {
Frank Tang1f164ee2022-11-08 12:31:27 -08001728 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001729 }
1730 return (UBool)(resB->fIndex < resB->fSize-1);
1731}
1732
1733U_CAPI const UChar* U_EXPORT2 ures_getNextString(UResourceBundle *resB, int32_t* len, const char ** key, UErrorCode *status) {
1734 Resource r = RES_BOGUS;
1735
1736 if (status==NULL || U_FAILURE(*status)) {
1737 return NULL;
1738 }
1739 if(resB == NULL) {
1740 *status = U_ILLEGAL_ARGUMENT_ERROR;
1741 return NULL;
1742 }
1743
1744 if(resB->fIndex == resB->fSize-1) {
1745 *status = U_INDEX_OUTOFBOUNDS_ERROR;
1746 } else {
1747 resB->fIndex++;
1748 switch(RES_GET_TYPE(resB->fRes)) {
1749 case URES_STRING:
1750 case URES_STRING_V2:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001751 return res_getString({resB}, &resB->getResData(), resB->fRes, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001752 case URES_TABLE:
1753 case URES_TABLE16:
1754 case URES_TABLE32:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001755 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, resB->fIndex, key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001756 if(r == RES_BOGUS && resB->fHasFallback) {
1757 /* TODO: do the fallback */
1758 }
1759 return ures_getStringWithAlias(resB, r, resB->fIndex, len, status);
1760 case URES_ARRAY:
1761 case URES_ARRAY16:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001762 r = res_getArrayItem(&resB->getResData(), resB->fRes, resB->fIndex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001763 if(r == RES_BOGUS && resB->fHasFallback) {
1764 /* TODO: do the fallback */
1765 }
1766 return ures_getStringWithAlias(resB, r, resB->fIndex, len, status);
1767 case URES_ALIAS:
1768 return ures_getStringWithAlias(resB, resB->fRes, resB->fIndex, len, status);
1769 case URES_INT:
1770 case URES_BINARY:
1771 case URES_INT_VECTOR:
1772 *status = U_RESOURCE_TYPE_MISMATCH;
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07001773 U_FALLTHROUGH;
1774 default:
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001775 return NULL;
1776 }
1777 }
1778
1779 return NULL;
1780}
1781
1782U_CAPI UResourceBundle* U_EXPORT2 ures_getNextResource(UResourceBundle *resB, UResourceBundle *fillIn, UErrorCode *status) {
1783 const char *key = NULL;
1784 Resource r = RES_BOGUS;
1785
1786 if (status==NULL || U_FAILURE(*status)) {
1787 /*return NULL;*/
1788 return fillIn;
1789 }
1790 if(resB == NULL) {
1791 *status = U_ILLEGAL_ARGUMENT_ERROR;
1792 /*return NULL;*/
1793 return fillIn;
1794 }
1795
1796 if(resB->fIndex == resB->fSize-1) {
1797 *status = U_INDEX_OUTOFBOUNDS_ERROR;
1798 /*return NULL;*/
1799 } else {
1800 resB->fIndex++;
1801 switch(RES_GET_TYPE(resB->fRes)) {
1802 case URES_INT:
1803 case URES_BINARY:
1804 case URES_STRING:
1805 case URES_STRING_V2:
1806 case URES_INT_VECTOR:
1807 return ures_copyResb(fillIn, resB, status);
1808 case URES_TABLE:
1809 case URES_TABLE16:
1810 case URES_TABLE32:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001811 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, resB->fIndex, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001812 if(r == RES_BOGUS && resB->fHasFallback) {
1813 /* TODO: do the fallback */
1814 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001815 return init_resb_result(resB->fData, r, key, resB->fIndex, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001816 case URES_ARRAY:
1817 case URES_ARRAY16:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001818 r = res_getArrayItem(&resB->getResData(), resB->fRes, resB->fIndex);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001819 if(r == RES_BOGUS && resB->fHasFallback) {
1820 /* TODO: do the fallback */
1821 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001822 return init_resb_result(resB->fData, r, key, resB->fIndex, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001823 default:
1824 /*return NULL;*/
1825 return fillIn;
1826 }
1827 }
1828 /*return NULL;*/
1829 return fillIn;
1830}
1831
1832U_CAPI UResourceBundle* U_EXPORT2 ures_getByIndex(const UResourceBundle *resB, int32_t indexR, UResourceBundle *fillIn, UErrorCode *status) {
1833 const char* key = NULL;
1834 Resource r = RES_BOGUS;
1835
1836 if (status==NULL || U_FAILURE(*status)) {
1837 /*return NULL;*/
1838 return fillIn;
1839 }
1840 if(resB == NULL) {
1841 *status = U_ILLEGAL_ARGUMENT_ERROR;
1842 /*return NULL;*/
1843 return fillIn;
1844 }
1845
1846 if(indexR >= 0 && resB->fSize > indexR) {
1847 switch(RES_GET_TYPE(resB->fRes)) {
1848 case URES_INT:
1849 case URES_BINARY:
1850 case URES_STRING:
1851 case URES_STRING_V2:
1852 case URES_INT_VECTOR:
1853 return ures_copyResb(fillIn, resB, status);
1854 case URES_TABLE:
1855 case URES_TABLE16:
1856 case URES_TABLE32:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001857 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, indexR, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001858 if(r == RES_BOGUS && resB->fHasFallback) {
1859 /* TODO: do the fallback */
1860 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001861 return init_resb_result(resB->fData, r, key, indexR, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001862 case URES_ARRAY:
1863 case URES_ARRAY16:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001864 r = res_getArrayItem(&resB->getResData(), resB->fRes, indexR);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001865 if(r == RES_BOGUS && resB->fHasFallback) {
1866 /* TODO: do the fallback */
1867 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08001868 return init_resb_result(resB->fData, r, key, indexR, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001869 default:
1870 /*return NULL;*/
1871 return fillIn;
1872 }
1873 } else {
1874 *status = U_MISSING_RESOURCE_ERROR;
1875 }
1876 /*return NULL;*/
1877 return fillIn;
1878}
1879
1880U_CAPI const UChar* U_EXPORT2 ures_getStringByIndex(const UResourceBundle *resB, int32_t indexS, int32_t* len, UErrorCode *status) {
1881 const char* key = NULL;
1882 Resource r = RES_BOGUS;
1883
1884 if (status==NULL || U_FAILURE(*status)) {
1885 return NULL;
1886 }
1887 if(resB == NULL) {
1888 *status = U_ILLEGAL_ARGUMENT_ERROR;
1889 return NULL;
1890 }
1891
1892 if(indexS >= 0 && resB->fSize > indexS) {
1893 switch(RES_GET_TYPE(resB->fRes)) {
1894 case URES_STRING:
1895 case URES_STRING_V2:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001896 return res_getString({resB}, &resB->getResData(), resB->fRes, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001897 case URES_TABLE:
1898 case URES_TABLE16:
1899 case URES_TABLE32:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001900 r = res_getTableItemByIndex(&resB->getResData(), resB->fRes, indexS, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001901 if(r == RES_BOGUS && resB->fHasFallback) {
1902 /* TODO: do the fallback */
1903 }
1904 return ures_getStringWithAlias(resB, r, indexS, len, status);
1905 case URES_ARRAY:
1906 case URES_ARRAY16:
Frank Tang3e05d9d2021-11-08 14:04:04 -08001907 r = res_getArrayItem(&resB->getResData(), resB->fRes, indexS);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00001908 if(r == RES_BOGUS && resB->fHasFallback) {
1909 /* TODO: do the fallback */
1910 }
1911 return ures_getStringWithAlias(resB, r, indexS, len, status);
1912 case URES_ALIAS:
1913 return ures_getStringWithAlias(resB, resB->fRes, indexS, len, status);
1914 case URES_INT:
1915 case URES_BINARY:
1916 case URES_INT_VECTOR:
1917 *status = U_RESOURCE_TYPE_MISMATCH;
1918 break;
1919 default:
1920 /* must not occur */
1921 *status = U_INTERNAL_PROGRAM_ERROR;
1922 break;
1923 }
1924 } else {
1925 *status = U_MISSING_RESOURCE_ERROR;
1926 }
1927 return NULL;
1928}
1929
1930U_CAPI const char * U_EXPORT2
1931ures_getUTF8StringByIndex(const UResourceBundle *resB,
1932 int32_t idx,
1933 char *dest, int32_t *pLength,
1934 UBool forceCopy,
1935 UErrorCode *status) {
1936 int32_t length16;
1937 const UChar *s16 = ures_getStringByIndex(resB, idx, &length16, status);
1938 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
1939}
1940
1941/*U_CAPI const char *ures_getResPath(UResourceBundle *resB) {
1942 return resB->fResPath;
1943}*/
1944
1945U_CAPI UResourceBundle* U_EXPORT2
1946ures_findResource(const char* path, UResourceBundle *fillIn, UErrorCode *status)
1947{
1948 UResourceBundle *first = NULL;
1949 UResourceBundle *result = fillIn;
1950 char *packageName = NULL;
1951 char *pathToResource = NULL, *save = NULL;
1952 char *locale = NULL, *localeEnd = NULL;
1953 int32_t length;
1954
1955 if(status == NULL || U_FAILURE(*status)) {
1956 return result;
1957 }
1958
1959 length = (int32_t)(uprv_strlen(path)+1);
1960 save = pathToResource = (char *)uprv_malloc(length*sizeof(char));
1961 /* test for NULL */
1962 if(pathToResource == NULL) {
1963 *status = U_MEMORY_ALLOCATION_ERROR;
1964 return result;
1965 }
1966 uprv_memcpy(pathToResource, path, length);
1967
1968 locale = pathToResource;
1969 if(*pathToResource == RES_PATH_SEPARATOR) { /* there is a path specification */
1970 pathToResource++;
1971 packageName = pathToResource;
1972 pathToResource = uprv_strchr(pathToResource, RES_PATH_SEPARATOR);
1973 if(pathToResource == NULL) {
1974 *status = U_ILLEGAL_ARGUMENT_ERROR;
1975 } else {
1976 *pathToResource = 0;
1977 locale = pathToResource+1;
1978 }
1979 }
1980
1981 localeEnd = uprv_strchr(locale, RES_PATH_SEPARATOR);
1982 if(localeEnd != NULL) {
1983 *localeEnd = 0;
1984 }
1985
1986 first = ures_open(packageName, locale, status);
1987
1988 if(U_SUCCESS(*status)) {
1989 if(localeEnd) {
1990 result = ures_findSubResource(first, localeEnd+1, fillIn, status);
1991 } else {
1992 result = ures_copyResb(fillIn, first, status);
1993 }
1994 ures_close(first);
1995 }
1996 uprv_free(save);
1997 return result;
1998}
1999
2000U_CAPI UResourceBundle* U_EXPORT2
2001ures_findSubResource(const UResourceBundle *resB, char* path, UResourceBundle *fillIn, UErrorCode *status)
2002{
2003 Resource res = RES_BOGUS;
2004 UResourceBundle *result = fillIn;
2005 const char *key;
2006
2007 if(status == NULL || U_FAILURE(*status)) {
2008 return result;
2009 }
2010
2011 /* here we do looping and circular alias checking */
2012 /* this loop is here because aliasing is resolved on this level, not on res level */
2013 /* so, when we encounter an alias, it is not an aggregate resource, so we return */
2014 do {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002015 res = res_findResource(&resB->getResData(), resB->fRes, &path, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002016 if(res != RES_BOGUS) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002017 result = init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002018 resB = result;
2019 } else {
2020 *status = U_MISSING_RESOURCE_ERROR;
2021 break;
2022 }
2023 } while(*path); /* there is more stuff in the path */
2024
2025 return result;
2026}
Frank Tangf90543d2020-10-30 19:02:04 -07002027U_CAPI const UChar* U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002028ures_getStringByKeyWithFallback(const UResourceBundle *resB,
2029 const char* inKey,
2030 int32_t* len,
2031 UErrorCode *status) {
2032
2033 UResourceBundle stack;
2034 const UChar* retVal = NULL;
2035 ures_initStackObject(&stack);
2036 ures_getByKeyWithFallback(resB, inKey, &stack, status);
2037 int32_t length;
2038 retVal = ures_getString(&stack, &length, status);
2039 ures_close(&stack);
2040 if (U_FAILURE(*status)) {
2041 return NULL;
2042 }
2043 if (length == 3 && retVal[0] == EMPTY_SET && retVal[1] == EMPTY_SET && retVal[2] == EMPTY_SET ) {
2044 retVal = NULL;
2045 length = 0;
2046 *status = U_MISSING_RESOURCE_ERROR;
2047 }
2048 if (len != NULL) {
2049 *len = length;
2050 }
2051 return retVal;
2052}
2053
2054/*
2055 Like res_getTableItemByKey but accepts full paths like "NumberElements/latn/patternsShort".
2056*/
2057static Resource getTableItemByKeyPath(const ResourceData *pResData, Resource table, const char *key) {
2058 Resource resource = table; /* The current resource */
2059 icu::CharString path;
2060 UErrorCode errorCode = U_ZERO_ERROR;
2061 path.append(key, errorCode);
2062 if (U_FAILURE(errorCode)) { return RES_BOGUS; }
2063 char *pathPart = path.data(); /* Path from current resource to desired resource */
2064 UResType type = (UResType)RES_GET_TYPE(resource); /* the current resource type */
2065 while (*pathPart && resource != RES_BOGUS && URES_IS_CONTAINER(type)) {
2066 char *nextPathPart = uprv_strchr(pathPart, RES_PATH_SEPARATOR);
2067 if (nextPathPart != NULL) {
2068 *nextPathPart = 0; /* Terminating null for this part of path. */
2069 nextPathPart++;
2070 } else {
2071 nextPathPart = uprv_strchr(pathPart, 0);
2072 }
2073 int32_t t;
2074 const char *pathP = pathPart;
2075 resource = res_getTableItemByKey(pResData, resource, &t, &pathP);
2076 type = (UResType)RES_GET_TYPE(resource);
2077 pathPart = nextPathPart;
2078 }
2079 if (*pathPart) {
2080 return RES_BOGUS;
2081 }
2082 return resource;
2083}
2084
Frank Tang3e05d9d2021-11-08 14:04:04 -08002085static void createPath(const char* origResPath,
2086 int32_t origResPathLen,
2087 const char* resPath,
2088 int32_t resPathLen,
2089 const char* inKey,
2090 CharString& path,
2091 UErrorCode* status) {
2092 // This is a utility function used by ures_getByKeyWithFallback() below. This function builds a path from
2093 // resPath and inKey, returning the result in `path`. Originally, this function just cleared `path` and
2094 // appended resPath and inKey to it, but that caused problems for horizontal inheritance.
2095 //
2096 // In normal cases, resPath is the same as origResPath, but if ures_getByKeyWithFallback() has followed an
2097 // alias, resPath may be different from origResPath. Not only may the existing path elements be different,
2098 // but resPath may also have MORE path elements than origResPath did. If it does, those additional path
2099 // elements SUPERSEDE the corresponding elements of inKey. So this code counts the number of elements in
2100 // resPath and origResPath and, for each path element in resPath that doesn't have a counterpart in origResPath,
2101 // deletes a path element from the beginning of inKey. The remainder of inKey is then appended to
2102 // resPath to form the result. (We're not using uprv_strchr() here because resPath and origResPath may
2103 // not be zero-terminated.)
2104 path.clear();
2105 const char* key = inKey;
2106 if (resPathLen > 0) {
2107 path.append(resPath, resPathLen, *status);
2108 if (U_SUCCESS(*status)) {
2109 const char* resPathLimit = resPath + resPathLen;
2110 const char* origResPathLimit = origResPath + origResPathLen;
2111 const char* resPathPtr = resPath;
2112 const char* origResPathPtr = origResPath;
2113
2114 // Remove from the beginning of resPath the number of segments that are contained in origResPath.
2115 // If origResPath has MORE segments than resPath, this will leave resPath as the empty string.
2116 while (origResPathPtr < origResPathLimit && resPathPtr < resPathLimit) {
2117 while (origResPathPtr < origResPathLimit && *origResPathPtr != RES_PATH_SEPARATOR) {
2118 ++origResPathPtr;
2119 }
2120 if (origResPathPtr < origResPathLimit && *origResPathPtr == RES_PATH_SEPARATOR) {
2121 ++origResPathPtr;
2122 }
2123 while (resPathPtr < resPathLimit && *resPathPtr != RES_PATH_SEPARATOR) {
2124 ++resPathPtr;
2125 }
2126 if (resPathPtr < resPathLimit && *resPathPtr == RES_PATH_SEPARATOR) {
2127 ++resPathPtr;
2128 }
2129 }
2130
2131 // New remove from the beginning of `key` the number of segments remaining in resPath.
2132 // If resPath has more segments than `key` does, `key` will end up empty.
2133 while (resPathPtr < resPathLimit && *key != '\0') {
2134 while (resPathPtr < resPathLimit && *resPathPtr != RES_PATH_SEPARATOR) {
2135 ++resPathPtr;
2136 }
2137 if (resPathPtr < resPathLimit && *resPathPtr == RES_PATH_SEPARATOR) {
2138 ++resPathPtr;
2139 }
2140 while (*key != '\0' && *key != RES_PATH_SEPARATOR) {
2141 ++key;
2142 }
2143 if (*key == RES_PATH_SEPARATOR) {
2144 ++key;
2145 }
2146 }
2147 }
2148 // Finally, append what's left of `key` to `path`. What you end up with here is `resPath`, plus
2149 // any pieces of `key` that aren't superseded by `resPath`.
2150 // Or, to put it another way, calculate <#-segments-in-key> - (<#-segments-in-resPath> - <#-segments-in-origResPath>),
2151 // and append that many segments from the end of `key` to `resPath` to produce the result.
2152 path.append(key, *status);
2153 } else {
2154 path.append(inKey, *status);
2155 }
2156}
2157
2158U_CAPI UResourceBundle* U_EXPORT2
2159ures_getByKeyWithFallback(const UResourceBundle *resB,
2160 const char* inKey,
2161 UResourceBundle *fillIn,
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002162 UErrorCode *status) {
2163 Resource res = RES_BOGUS, rootRes = RES_BOGUS;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002164 UResourceBundle *helper = NULL;
2165
2166 if (status==NULL || U_FAILURE(*status)) {
2167 return fillIn;
2168 }
2169 if(resB == NULL) {
2170 *status = U_ILLEGAL_ARGUMENT_ERROR;
2171 return fillIn;
2172 }
2173
2174 int32_t type = RES_GET_TYPE(resB->fRes);
2175 if(URES_IS_TABLE(type)) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002176 const char* origResPath = resB->fResPath;
2177 int32_t origResPathLen = resB->fResPathLen;
2178 res = getTableItemByKeyPath(&resB->getResData(), resB->fRes, inKey);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002179 const char* key = inKey;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002180 bool didRootOnce = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002181 if(res == RES_BOGUS) {
2182 UResourceDataEntry *dataEntry = resB->fData;
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08002183 CharString path;
2184 char *myPath = NULL;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002185 const char* resPath = resB->fResPath;
2186 int32_t len = resB->fResPathLen;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002187 while(res == RES_BOGUS && (dataEntry->fParent != NULL || !didRootOnce)) { /* Otherwise, we'll look in parents */
2188 if (dataEntry->fParent != NULL) {
2189 dataEntry = dataEntry->fParent;
2190 } else {
2191 // We can't just stop when we get to a bundle whose fParent is NULL. That'll work most of the time,
2192 // but if the bundle that the caller passed to us was "root" (which happens in getAllItemsWithFallback(),
2193 // this function will drop right out without doing anything if "root" doesn't contain the exact key path
2194 // specified. In that case, we need one extra time through this loop to make sure we follow any
2195 // applicable aliases at the root level.
2196 didRootOnce = true;
2197 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002198 rootRes = dataEntry->fData.rootRes;
2199
2200 if(dataEntry->fBogus == U_ZERO_ERROR) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002201 createPath(origResPath, origResPathLen, resPath, len, inKey, path, status);
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08002202 if (U_FAILURE(*status)) {
2203 ures_close(helper);
2204 return fillIn;
2205 }
2206 myPath = path.data();
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002207 key = inKey;
2208 do {
2209 res = res_findResource(&(dataEntry->fData), rootRes, &myPath, &key);
2210 if (RES_GET_TYPE(res) == URES_ALIAS && *myPath) {
2211 /* We hit an alias, but we didn't finish following the path. */
Frank Tang3e05d9d2021-11-08 14:04:04 -08002212 helper = init_resb_result(dataEntry, res, NULL, -1, resB, helper, status);
2213 /*helper = init_resb_result(dataEntry, res, inKey, -1, resB, helper, status);*/
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002214 if(helper) {
2215 dataEntry = helper->fData;
2216 rootRes = helper->fRes;
2217 resPath = helper->fResPath;
2218 len = helper->fResPathLen;
2219
2220 } else {
2221 break;
2222 }
Frank Tang7e7574b2021-04-13 21:19:13 -07002223 } else if (res == RES_BOGUS) {
2224 break;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002225 }
2226 } while(*myPath); /* Continue until the whole path is consumed */
2227 }
2228 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08002229 /*dataEntry = getFallbackData(resB, &key, &res, status);*/
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002230 if(res != RES_BOGUS) {
2231 /* check if resB->fResPath gives the right name here */
2232 if(uprv_strcmp(dataEntry->fName, uloc_getDefault())==0 || uprv_strcmp(dataEntry->fName, kRootLocaleName)==0) {
2233 *status = U_USING_DEFAULT_WARNING;
2234 } else {
2235 *status = U_USING_FALLBACK_WARNING;
2236 }
2237
Frank Tang3e05d9d2021-11-08 14:04:04 -08002238 fillIn = init_resb_result(dataEntry, res, key, -1, resB, fillIn, status);
2239 if (resPath != nullptr) {
2240 createPath(origResPath, origResPathLen, resPath, len, inKey, path, status);
2241 } else {
2242 const char* separator = nullptr;
2243 if (fillIn->fResPath != nullptr) {
2244 separator = uprv_strchr(fillIn->fResPath, RES_PATH_SEPARATOR);
2245 }
2246 if (separator != nullptr && separator[1] != '\0') {
2247 createPath(origResPath, origResPathLen, fillIn->fResPath,
2248 static_cast<int32_t>(uprv_strlen(fillIn->fResPath)), inKey, path, status);
2249 } else {
2250 createPath(origResPath, origResPathLen, "", 0, inKey, path, status);
2251 }
2252 }
2253 ures_freeResPath(fillIn);
2254 ures_appendResPath(fillIn, path.data(), path.length(), status);
2255 if(fillIn->fResPath[fillIn->fResPathLen-1] != RES_PATH_SEPARATOR) {
2256 ures_appendResPath(fillIn, RES_PATH_SEPARATOR_S, 1, status);
2257 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002258 } else {
2259 *status = U_MISSING_RESOURCE_ERROR;
2260 }
2261 } else {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002262 fillIn = init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002263 }
2264 }
2265 else {
2266 *status = U_RESOURCE_TYPE_MISMATCH;
2267 }
2268 ures_close(helper);
2269 return fillIn;
2270}
2271
Jungshik Shin825221b2016-01-29 01:05:51 -08002272namespace {
2273
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002274void getAllItemsWithFallback(
Jungshik Shin825221b2016-01-29 01:05:51 -08002275 const UResourceBundle *bundle, ResourceDataValue &value,
Frank Tang3e05d9d2021-11-08 14:04:04 -08002276 ResourceSink &sink, UErrorCode &errorCode) {
Jungshik Shin825221b2016-01-29 01:05:51 -08002277 if (U_FAILURE(errorCode)) { return; }
2278 // We recursively enumerate child-first,
2279 // only storing parent items in the absence of child items.
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002280 // The sink needs to store a placeholder value for the no-fallback/no-inheritance marker
Jungshik Shin825221b2016-01-29 01:05:51 -08002281 // to prevent a parent item from being stored.
2282 //
2283 // It would be possible to recursively enumerate parent-first,
2284 // overriding parent items with child items.
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002285 // When the sink sees the no-fallback/no-inheritance marker,
2286 // then it would remove the parent's item.
Jungshik Shin825221b2016-01-29 01:05:51 -08002287 // We would deserialize parent values even though they are overridden in a child bundle.
Frank Tang3e05d9d2021-11-08 14:04:04 -08002288 value.setData(bundle->getResData());
2289 value.setValidLocaleDataEntry(bundle->fValidLocaleDataEntry);
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002290 UResourceDataEntry *parentEntry = bundle->fData->fParent;
2291 UBool hasParent = parentEntry != NULL && U_SUCCESS(parentEntry->fBogus);
Frank Tang952ccb92019-08-22 12:09:17 -07002292 value.setResource(bundle->fRes, ResourceTracer(bundle));
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002293 sink.put(bundle->fKey, value, !hasParent, errorCode);
2294 if (hasParent) {
Jungshik Shin825221b2016-01-29 01:05:51 -08002295 // We might try to query the sink whether
2296 // any fallback from the parent bundle is still possible.
2297
2298 // Turn the parent UResourceDataEntry into a UResourceBundle,
2299 // much like in ures_openWithType().
2300 // TODO: See if we can refactor ures_getByKeyWithFallback()
2301 // and pull out an inner function that takes and returns a UResourceDataEntry
2302 // so that we need not create UResourceBundle objects.
Frank Tang3e05d9d2021-11-08 14:04:04 -08002303 StackUResourceBundle parentBundle;
2304 UResourceBundle &parentRef = parentBundle.ref();
2305 parentRef.fData = parentEntry;
2306 parentRef.fValidLocaleDataEntry = bundle->fValidLocaleDataEntry;
2307 parentRef.fHasFallback = !parentRef.getResData().noFallback;
Frank Tang1f164ee2022-11-08 12:31:27 -08002308 parentRef.fIsTopLevel = true;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002309 parentRef.fRes = parentRef.getResData().rootRes;
2310 parentRef.fSize = res_countArrayItems(&parentRef.getResData(), parentRef.fRes);
2311 parentRef.fIndex = -1;
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002312 entryIncrease(parentEntry);
Jungshik Shin825221b2016-01-29 01:05:51 -08002313
2314 // Look up the container item in the parent bundle.
Frank Tang3e05d9d2021-11-08 14:04:04 -08002315 StackUResourceBundle containerBundle;
Jungshik Shin825221b2016-01-29 01:05:51 -08002316 const UResourceBundle *rb;
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002317 UErrorCode pathErrorCode = U_ZERO_ERROR; // Ignore if parents up to root do not have this path.
Jungshik Shin825221b2016-01-29 01:05:51 -08002318 if (bundle->fResPath == NULL || *bundle->fResPath == 0) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002319 rb = parentBundle.getAlias();
Jungshik Shin825221b2016-01-29 01:05:51 -08002320 } else {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002321 rb = ures_getByKeyWithFallback(parentBundle.getAlias(), bundle->fResPath,
2322 containerBundle.getAlias(), &pathErrorCode);
Jungshik Shin825221b2016-01-29 01:05:51 -08002323 }
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002324 if (U_SUCCESS(pathErrorCode)) {
2325 getAllItemsWithFallback(rb, value, sink, errorCode);
Jungshik Shin825221b2016-01-29 01:05:51 -08002326 }
Jungshik Shin825221b2016-01-29 01:05:51 -08002327 }
2328}
2329
Frank Tang3e05d9d2021-11-08 14:04:04 -08002330struct GetAllChildrenSink : public ResourceSink {
2331 // Destination sink
2332 ResourceSink& dest;
2333
2334 GetAllChildrenSink(ResourceSink& dest)
2335 : dest(dest) {}
2336 virtual ~GetAllChildrenSink() override;
2337 virtual void put(const char *key, ResourceValue &value, UBool isRoot,
2338 UErrorCode &errorCode) override {
2339 ResourceTable itemsTable = value.getTable(errorCode);
2340 if (U_FAILURE(errorCode)) { return; }
2341 for (int32_t i = 0; itemsTable.getKeyAndValue(i, key, value); ++i) {
2342 if (value.getType() == URES_ALIAS) {
2343 ResourceDataValue& rdv = static_cast<ResourceDataValue&>(value);
2344 StackUResourceBundle stackTempBundle;
2345 UResourceBundle* aliasRB = getAliasTargetAsResourceBundle(rdv.getData(), rdv.getResource(), nullptr, -1,
2346 rdv.getValidLocaleDataEntry(), nullptr, 0,
2347 stackTempBundle.getAlias(), &errorCode);
2348 if (U_SUCCESS(errorCode)) {
2349 ResourceDataValue aliasedValue;
2350 aliasedValue.setData(aliasRB->getResData());
2351 aliasedValue.setValidLocaleDataEntry(aliasRB->fValidLocaleDataEntry);
2352 aliasedValue.setResource(aliasRB->fRes, ResourceTracer(aliasRB));
2353 dest.put(key, aliasedValue, isRoot, errorCode);
2354 }
2355 } else {
2356 dest.put(key, value, isRoot, errorCode);
2357 }
2358 if (U_FAILURE(errorCode)) { return; }
2359 }
2360 }
2361};
2362
2363// Virtual destructors must be defined out of line.
2364GetAllChildrenSink::~GetAllChildrenSink() {}
2365
2366U_CAPI void U_EXPORT2
2367ures_getAllChildrenWithFallback(const UResourceBundle *bundle, const char *path,
2368 icu::ResourceSink &sink, UErrorCode &errorCode) {
2369 GetAllChildrenSink allChildrenSink(sink);
2370 ures_getAllItemsWithFallback(bundle, path, allChildrenSink, errorCode);
2371}
2372
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002373} // namespace
2374
Frank Tang952ccb92019-08-22 12:09:17 -07002375// Requires a ResourceDataValue fill-in, so that we need not cast from a ResourceValue.
2376// Unfortunately, the caller must know which subclass to make and pass in.
2377// Alternatively, we could make it as polymorphic as in Java by
2378// returning a ResourceValue pointer (possibly wrapped into a LocalPointer)
2379// that the caller then owns.
2380//
2381// Also requires a UResourceBundle fill-in, so that the value's ResourceTracer
2382// can point to a non-local bundle.
2383// Without tracing, the child bundle could be a function-local object.
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002384U_CAPI void U_EXPORT2
Frank Tang952ccb92019-08-22 12:09:17 -07002385ures_getValueWithFallback(const UResourceBundle *bundle, const char *path,
2386 UResourceBundle *tempFillIn,
2387 ResourceDataValue &value, UErrorCode &errorCode) {
Jungshik Shin825221b2016-01-29 01:05:51 -08002388 if (U_FAILURE(errorCode)) { return; }
Frank Tang952ccb92019-08-22 12:09:17 -07002389 if (path == nullptr) {
Jungshik Shin825221b2016-01-29 01:05:51 -08002390 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
2391 return;
2392 }
Jungshik Shin825221b2016-01-29 01:05:51 -08002393 const UResourceBundle *rb;
2394 if (*path == 0) {
2395 // empty path
2396 rb = bundle;
2397 } else {
Frank Tang952ccb92019-08-22 12:09:17 -07002398 rb = ures_getByKeyWithFallback(bundle, path, tempFillIn, &errorCode);
Jungshik Shin825221b2016-01-29 01:05:51 -08002399 if (U_FAILURE(errorCode)) {
Frank Tang952ccb92019-08-22 12:09:17 -07002400 return;
2401 }
2402 }
Frank Tang3e05d9d2021-11-08 14:04:04 -08002403 value.setData(rb->getResData());
2404 value.setValidLocaleDataEntry(rb->fValidLocaleDataEntry);
Frank Tang952ccb92019-08-22 12:09:17 -07002405 value.setResource(rb->fRes, ResourceTracer(rb));
2406}
2407
2408U_CAPI void U_EXPORT2
2409ures_getAllItemsWithFallback(const UResourceBundle *bundle, const char *path,
2410 icu::ResourceSink &sink, UErrorCode &errorCode) {
2411 if (U_FAILURE(errorCode)) { return; }
2412 if (path == nullptr) {
2413 errorCode = U_ILLEGAL_ARGUMENT_ERROR;
2414 return;
2415 }
2416 StackUResourceBundle stackBundle;
2417 const UResourceBundle *rb;
2418 if (*path == 0) {
2419 // empty path
2420 rb = bundle;
2421 } else {
2422 rb = ures_getByKeyWithFallback(bundle, path, stackBundle.getAlias(), &errorCode);
2423 if (U_FAILURE(errorCode)) {
Jungshik Shin825221b2016-01-29 01:05:51 -08002424 return;
2425 }
2426 }
Jungshik Shin825221b2016-01-29 01:05:51 -08002427 // Get all table items with fallback.
2428 ResourceDataValue value;
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002429 getAllItemsWithFallback(rb, value, sink, errorCode);
Jungshik Shin825221b2016-01-29 01:05:51 -08002430}
2431
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002432U_CAPI UResourceBundle* U_EXPORT2 ures_getByKey(const UResourceBundle *resB, const char* inKey, UResourceBundle *fillIn, UErrorCode *status) {
2433 Resource res = RES_BOGUS;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002434 UResourceDataEntry *dataEntry = NULL;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002435 const char *key = inKey;
2436
2437 if (status==NULL || U_FAILURE(*status)) {
2438 return fillIn;
2439 }
2440 if(resB == NULL) {
2441 *status = U_ILLEGAL_ARGUMENT_ERROR;
2442 return fillIn;
2443 }
2444
2445 int32_t type = RES_GET_TYPE(resB->fRes);
2446 if(URES_IS_TABLE(type)) {
2447 int32_t t;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002448 res = res_getTableItemByKey(&resB->getResData(), resB->fRes, &t, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002449 if(res == RES_BOGUS) {
2450 key = inKey;
Frank Tang1f164ee2022-11-08 12:31:27 -08002451 if(resB->fHasFallback == true) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002452 dataEntry = getFallbackData(resB, &key, &res, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002453 if(U_SUCCESS(*status)) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002454 /* check if resB->fResPath gives the right name here */
2455 return init_resb_result(dataEntry, res, key, -1, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002456 } else {
2457 *status = U_MISSING_RESOURCE_ERROR;
2458 }
2459 } else {
2460 *status = U_MISSING_RESOURCE_ERROR;
2461 }
2462 } else {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002463 return init_resb_result(resB->fData, res, key, -1, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002464 }
2465 }
2466#if 0
2467 /* this is a kind of TODO item. If we have an array with an index table, we could do this. */
2468 /* not currently */
Frank Tang1f164ee2022-11-08 12:31:27 -08002469 else if(RES_GET_TYPE(resB->fRes) == URES_ARRAY && resB->fHasFallback == true) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002470 /* here should go a first attempt to locate the key using index table */
Frank Tang3e05d9d2021-11-08 14:04:04 -08002471 dataEntry = getFallbackData(resB, &key, &res, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002472 if(U_SUCCESS(*status)) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002473 return init_resb_result(dataEntry, res, key, resB, fillIn, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002474 } else {
2475 *status = U_MISSING_RESOURCE_ERROR;
2476 }
2477 }
2478#endif
2479 else {
2480 *status = U_RESOURCE_TYPE_MISMATCH;
2481 }
2482 return fillIn;
2483}
2484
2485U_CAPI const UChar* U_EXPORT2 ures_getStringByKey(const UResourceBundle *resB, const char* inKey, int32_t* len, UErrorCode *status) {
2486 Resource res = RES_BOGUS;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002487 UResourceDataEntry *dataEntry = NULL;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002488 const char* key = inKey;
2489
2490 if (status==NULL || U_FAILURE(*status)) {
2491 return NULL;
2492 }
2493 if(resB == NULL) {
2494 *status = U_ILLEGAL_ARGUMENT_ERROR;
2495 return NULL;
2496 }
2497
2498 int32_t type = RES_GET_TYPE(resB->fRes);
2499 if(URES_IS_TABLE(type)) {
2500 int32_t t=0;
2501
Frank Tang3e05d9d2021-11-08 14:04:04 -08002502 res = res_getTableItemByKey(&resB->getResData(), resB->fRes, &t, &key);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002503
2504 if(res == RES_BOGUS) {
2505 key = inKey;
Frank Tang1f164ee2022-11-08 12:31:27 -08002506 if(resB->fHasFallback == true) {
Frank Tang3e05d9d2021-11-08 14:04:04 -08002507 dataEntry = getFallbackData(resB, &key, &res, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002508 if(U_SUCCESS(*status)) {
2509 switch (RES_GET_TYPE(res)) {
2510 case URES_STRING:
2511 case URES_STRING_V2:
Frank Tang3e05d9d2021-11-08 14:04:04 -08002512 return res_getString({resB, key}, &dataEntry->fData, res, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002513 case URES_ALIAS:
2514 {
2515 const UChar* result = 0;
2516 UResourceBundle *tempRes = ures_getByKey(resB, inKey, NULL, status);
2517 result = ures_getString(tempRes, len, status);
2518 ures_close(tempRes);
2519 return result;
2520 }
2521 default:
2522 *status = U_RESOURCE_TYPE_MISMATCH;
2523 }
2524 } else {
2525 *status = U_MISSING_RESOURCE_ERROR;
2526 }
2527 } else {
2528 *status = U_MISSING_RESOURCE_ERROR;
2529 }
2530 } else {
2531 switch (RES_GET_TYPE(res)) {
2532 case URES_STRING:
2533 case URES_STRING_V2:
Frank Tang3e05d9d2021-11-08 14:04:04 -08002534 return res_getString({resB, key}, &resB->getResData(), res, len);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002535 case URES_ALIAS:
2536 {
2537 const UChar* result = 0;
2538 UResourceBundle *tempRes = ures_getByKey(resB, inKey, NULL, status);
2539 result = ures_getString(tempRes, len, status);
2540 ures_close(tempRes);
2541 return result;
2542 }
2543 default:
2544 *status = U_RESOURCE_TYPE_MISMATCH;
2545 }
2546 }
2547 }
2548#if 0
2549 /* this is a kind of TODO item. If we have an array with an index table, we could do this. */
2550 /* not currently */
Frank Tang1f164ee2022-11-08 12:31:27 -08002551 else if(RES_GET_TYPE(resB->fRes) == URES_ARRAY && resB->fHasFallback == true) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002552 /* here should go a first attempt to locate the key using index table */
Frank Tang3e05d9d2021-11-08 14:04:04 -08002553 dataEntry = getFallbackData(resB, &key, &res, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002554 if(U_SUCCESS(*status)) {
Frank Tang952ccb92019-08-22 12:09:17 -07002555 // TODO: Tracing
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002556 return res_getString(rd, res, len);
2557 } else {
2558 *status = U_MISSING_RESOURCE_ERROR;
2559 }
2560 }
2561#endif
2562 else {
2563 *status = U_RESOURCE_TYPE_MISMATCH;
2564 }
2565 return NULL;
2566}
2567
2568U_CAPI const char * U_EXPORT2
2569ures_getUTF8StringByKey(const UResourceBundle *resB,
2570 const char *key,
2571 char *dest, int32_t *pLength,
2572 UBool forceCopy,
2573 UErrorCode *status) {
2574 int32_t length16;
2575 const UChar *s16 = ures_getStringByKey(resB, key, &length16, status);
2576 return ures_toUTF8String(s16, length16, dest, pLength, forceCopy, status);
2577}
2578
2579/* TODO: clean from here down */
2580
2581/**
2582 * INTERNAL: Get the name of the first real locale (not placeholder)
2583 * that has resource bundle data.
2584 */
Frank Tangf90543d2020-10-30 19:02:04 -07002585U_CAPI const char* U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002586ures_getLocaleInternal(const UResourceBundle* resourceBundle, UErrorCode* status)
2587{
2588 if (status==NULL || U_FAILURE(*status)) {
2589 return NULL;
2590 }
2591 if (!resourceBundle) {
2592 *status = U_ILLEGAL_ARGUMENT_ERROR;
2593 return NULL;
2594 } else {
2595 return resourceBundle->fData->fName;
2596 }
2597}
2598
2599U_CAPI const char* U_EXPORT2
2600ures_getLocale(const UResourceBundle* resourceBundle,
2601 UErrorCode* status)
2602{
2603 return ures_getLocaleInternal(resourceBundle, status);
2604}
2605
2606
2607U_CAPI const char* U_EXPORT2
2608ures_getLocaleByType(const UResourceBundle* resourceBundle,
2609 ULocDataLocaleType type,
2610 UErrorCode* status) {
2611 if (status==NULL || U_FAILURE(*status)) {
2612 return NULL;
2613 }
2614 if (!resourceBundle) {
2615 *status = U_ILLEGAL_ARGUMENT_ERROR;
2616 return NULL;
2617 } else {
2618 switch(type) {
2619 case ULOC_ACTUAL_LOCALE:
2620 return resourceBundle->fData->fName;
2621 case ULOC_VALID_LOCALE:
Frank Tang3e05d9d2021-11-08 14:04:04 -08002622 return resourceBundle->fValidLocaleDataEntry->fName;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002623 case ULOC_REQUESTED_LOCALE:
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002624 default:
2625 *status = U_ILLEGAL_ARGUMENT_ERROR;
2626 return NULL;
2627 }
2628 }
2629}
2630
2631U_CFUNC const char* ures_getName(const UResourceBundle* resB) {
2632 if(resB == NULL) {
2633 return NULL;
2634 }
2635
2636 return resB->fData->fName;
2637}
2638
2639#ifdef URES_DEBUG
2640U_CFUNC const char* ures_getPath(const UResourceBundle* resB) {
2641 if(resB == NULL) {
2642 return NULL;
2643 }
2644
2645 return resB->fData->fPath;
2646}
2647#endif
2648
Jungshik Shin70f82502016-01-29 00:32:36 -08002649static UResourceBundle*
2650ures_openWithType(UResourceBundle *r, const char* path, const char* localeID,
2651 UResOpenType openType, UErrorCode* status) {
2652 if(U_FAILURE(*status)) {
2653 return NULL;
2654 }
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002655
Jungshik Shin70f82502016-01-29 00:32:36 -08002656 UResourceDataEntry *entry;
2657 if(openType != URES_OPEN_DIRECT) {
2658 /* first "canonicalize" the locale ID */
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002659 char canonLocaleID[ULOC_FULLNAME_CAPACITY];
Jungshik Shin70f82502016-01-29 00:32:36 -08002660 uloc_getBaseName(localeID, canonLocaleID, UPRV_LENGTHOF(canonLocaleID), status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002661 if(U_FAILURE(*status) || *status == U_STRING_NOT_TERMINATED_WARNING) {
2662 *status = U_ILLEGAL_ARGUMENT_ERROR;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002663 return NULL;
2664 }
Jungshik Shin70f82502016-01-29 00:32:36 -08002665 entry = entryOpen(path, canonLocaleID, openType, status);
2666 } else {
2667 entry = entryOpenDirect(path, localeID, status);
2668 }
2669 if(U_FAILURE(*status)) {
2670 return NULL;
2671 }
2672 if(entry == NULL) {
2673 *status = U_MISSING_RESOURCE_ERROR;
2674 return NULL;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002675 }
2676
Jungshik Shin70f82502016-01-29 00:32:36 -08002677 UBool isStackObject;
2678 if(r == NULL) {
2679 r = (UResourceBundle *)uprv_malloc(sizeof(UResourceBundle));
2680 if(r == NULL) {
2681 entryClose(entry);
2682 *status = U_MEMORY_ALLOCATION_ERROR;
2683 return NULL;
2684 }
Frank Tang1f164ee2022-11-08 12:31:27 -08002685 isStackObject = false;
Jungshik Shin70f82502016-01-29 00:32:36 -08002686 } else { // fill-in
2687 isStackObject = ures_isStackObject(r);
Frank Tang1f164ee2022-11-08 12:31:27 -08002688 ures_closeBundle(r, false);
Jungshik Shin70f82502016-01-29 00:32:36 -08002689 }
2690 uprv_memset(r, 0, sizeof(UResourceBundle));
2691 ures_setIsStackObject(r, isStackObject);
2692
Frank Tang3e05d9d2021-11-08 14:04:04 -08002693 r->fValidLocaleDataEntry = r->fData = entry;
2694 r->fHasFallback = openType != URES_OPEN_DIRECT && !r->getResData().noFallback;
Frank Tang1f164ee2022-11-08 12:31:27 -08002695 r->fIsTopLevel = true;
Frank Tang3e05d9d2021-11-08 14:04:04 -08002696 r->fRes = r->getResData().rootRes;
2697 r->fSize = res_countArrayItems(&r->getResData(), r->fRes);
Jungshik Shin70f82502016-01-29 00:32:36 -08002698 r->fIndex = -1;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002699
Frank Tangb8696612019-10-25 14:58:21 -07002700 ResourceTracer(r).traceOpen();
2701
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002702 return r;
2703}
2704
Jungshik Shin70f82502016-01-29 00:32:36 -08002705U_CAPI UResourceBundle* U_EXPORT2
2706ures_open(const char* path, const char* localeID, UErrorCode* status) {
2707 return ures_openWithType(NULL, path, localeID, URES_OPEN_LOCALE_DEFAULT_ROOT, status);
2708}
2709
2710U_CAPI UResourceBundle* U_EXPORT2
2711ures_openNoDefault(const char* path, const char* localeID, UErrorCode* status) {
2712 return ures_openWithType(NULL, path, localeID, URES_OPEN_LOCALE_ROOT, status);
2713}
2714
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002715/**
2716 * Opens a resource bundle without "canonicalizing" the locale name. No fallback will be performed
2717 * or sought. However, alias substitution will happen!
2718 */
2719U_CAPI UResourceBundle* U_EXPORT2
2720ures_openDirect(const char* path, const char* localeID, UErrorCode* status) {
Jungshik Shin70f82502016-01-29 00:32:36 -08002721 return ures_openWithType(NULL, path, localeID, URES_OPEN_DIRECT, status);
2722}
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002723
Jungshik Shin70f82502016-01-29 00:32:36 -08002724/**
Frank Tang69c72a62019-04-03 21:41:21 -07002725 * Internal API: This function is used to open a resource bundle
Jungshik Shin70f82502016-01-29 00:32:36 -08002726 * proper fallback chaining is executed while initialization.
2727 * The result is stored in cache for later fallback search.
Frank Tang69c72a62019-04-03 21:41:21 -07002728 *
2729 * Same as ures_open(), but uses the fill-in parameter and does not allocate a new bundle.
Jungshik Shin70f82502016-01-29 00:32:36 -08002730 */
Frank Tangf90543d2020-10-30 19:02:04 -07002731U_CAPI void U_EXPORT2
Jungshik Shin70f82502016-01-29 00:32:36 -08002732ures_openFillIn(UResourceBundle *r, const char* path,
2733 const char* localeID, UErrorCode* status) {
2734 if(U_SUCCESS(*status) && r == NULL) {
2735 *status = U_ILLEGAL_ARGUMENT_ERROR;
2736 return;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002737 }
Jungshik Shin70f82502016-01-29 00:32:36 -08002738 ures_openWithType(r, path, localeID, URES_OPEN_LOCALE_DEFAULT_ROOT, status);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002739}
2740
2741/**
Frank Tang69c72a62019-04-03 21:41:21 -07002742 * Same as ures_openDirect(), but uses the fill-in parameter and does not allocate a new bundle.
2743 */
Frank Tangf90543d2020-10-30 19:02:04 -07002744U_CAPI void U_EXPORT2
Frank Tang69c72a62019-04-03 21:41:21 -07002745ures_openDirectFillIn(UResourceBundle *r, const char* path, const char* localeID, UErrorCode* status) {
2746 if(U_SUCCESS(*status) && r == NULL) {
2747 *status = U_ILLEGAL_ARGUMENT_ERROR;
2748 return;
2749 }
2750 ures_openWithType(r, path, localeID, URES_OPEN_DIRECT, status);
2751}
2752
2753/**
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002754 * API: Counts members. For arrays and tables, returns number of resources.
2755 * For strings, returns 1.
2756 */
2757U_CAPI int32_t U_EXPORT2
2758ures_countArrayItems(const UResourceBundle* resourceBundle,
2759 const char* resourceKey,
2760 UErrorCode* status)
2761{
2762 UResourceBundle resData;
2763 ures_initStackObject(&resData);
2764 if (status==NULL || U_FAILURE(*status)) {
2765 return 0;
2766 }
2767 if(resourceBundle == NULL) {
2768 *status = U_ILLEGAL_ARGUMENT_ERROR;
2769 return 0;
2770 }
2771 ures_getByKey(resourceBundle, resourceKey, &resData, status);
2772
Frank Tang3e05d9d2021-11-08 14:04:04 -08002773 if(resData.getResData().data != NULL) {
2774 int32_t result = res_countArrayItems(&resData.getResData(), resData.fRes);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002775 ures_close(&resData);
2776 return result;
2777 } else {
2778 *status = U_MISSING_RESOURCE_ERROR;
2779 ures_close(&resData);
2780 return 0;
2781 }
2782}
2783
2784/**
2785 * Internal function.
2786 * Return the version number associated with this ResourceBundle as a string.
2787 *
2788 * @param resourceBundle The resource bundle for which the version is checked.
2789 * @return A version number string as specified in the resource bundle or its parent.
2790 * The caller does not own this string.
2791 * @see ures_getVersion
2792 * @internal
2793 */
Frank Tangf90543d2020-10-30 19:02:04 -07002794U_CAPI const char* U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002795ures_getVersionNumberInternal(const UResourceBundle *resourceBundle)
2796{
2797 if (!resourceBundle) return NULL;
2798
2799 if(resourceBundle->fVersion == NULL) {
2800
2801 /* If the version ID has not been built yet, then do so. Retrieve */
2802 /* the minor version from the file. */
2803 UErrorCode status = U_ZERO_ERROR;
2804 int32_t minor_len = 0;
2805 int32_t len;
2806
2807 const UChar* minor_version = ures_getStringByKey(resourceBundle, kVersionTag, &minor_len, &status);
2808
2809 /* Determine the length of of the final version string. This is */
2810 /* the length of the major part + the length of the separator */
2811 /* (==1) + the length of the minor part (+ 1 for the zero byte at */
2812 /* the end). */
2813
2814 len = (minor_len > 0) ? minor_len : 1;
2815
2816 /* Allocate the string, and build it up. */
2817 /* + 1 for zero byte */
2818
2819
2820 ((UResourceBundle *)resourceBundle)->fVersion = (char *)uprv_malloc(1 + len);
2821 /* Check for null pointer. */
2822 if (((UResourceBundle *)resourceBundle)->fVersion == NULL) {
2823 return NULL;
2824 }
2825
2826 if(minor_len > 0) {
2827 u_UCharsToChars(minor_version, resourceBundle->fVersion , minor_len);
2828 resourceBundle->fVersion[len] = '\0';
2829 }
2830 else {
2831 uprv_strcpy(resourceBundle->fVersion, kDefaultMinorVersion);
2832 }
2833 }
2834
2835 return resourceBundle->fVersion;
2836}
2837
2838U_CAPI const char* U_EXPORT2
2839ures_getVersionNumber(const UResourceBundle* resourceBundle)
2840{
2841 return ures_getVersionNumberInternal(resourceBundle);
2842}
2843
2844U_CAPI void U_EXPORT2 ures_getVersion(const UResourceBundle* resB, UVersionInfo versionInfo) {
2845 if (!resB) return;
2846
2847 u_versionFromString(versionInfo, ures_getVersionNumberInternal(resB));
2848}
2849
2850/** Tree support functions *******************************/
2851#define INDEX_LOCALE_NAME "res_index"
2852#define INDEX_TAG "InstalledLocales"
2853#define DEFAULT_TAG "default"
2854
2855#if defined(URES_TREE_DEBUG)
2856#include <stdio.h>
2857#endif
2858
2859typedef struct ULocalesContext {
2860 UResourceBundle installed;
2861 UResourceBundle curr;
2862} ULocalesContext;
2863
2864static void U_CALLCONV
2865ures_loc_closeLocales(UEnumeration *enumerator) {
2866 ULocalesContext *ctx = (ULocalesContext *)enumerator->context;
2867 ures_close(&ctx->curr);
2868 ures_close(&ctx->installed);
2869 uprv_free(ctx);
2870 uprv_free(enumerator);
2871}
2872
2873static int32_t U_CALLCONV
2874ures_loc_countLocales(UEnumeration *en, UErrorCode * /*status*/) {
2875 ULocalesContext *ctx = (ULocalesContext *)en->context;
2876 return ures_getSize(&ctx->installed);
2877}
2878
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002879U_CDECL_BEGIN
2880
2881
2882static const char * U_CALLCONV
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002883ures_loc_nextLocale(UEnumeration* en,
2884 int32_t* resultLength,
2885 UErrorCode* status) {
2886 ULocalesContext *ctx = (ULocalesContext *)en->context;
2887 UResourceBundle *res = &(ctx->installed);
2888 UResourceBundle *k = NULL;
2889 const char *result = NULL;
2890 int32_t len = 0;
Jungshik Shin42d50272018-10-24 01:22:09 -07002891 if(ures_hasNext(res) && (k = ures_getNextResource(res, &ctx->curr, status)) != 0) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002892 result = ures_getKey(k);
2893 len = (int32_t)uprv_strlen(result);
2894 }
2895 if (resultLength) {
2896 *resultLength = len;
2897 }
2898 return result;
2899}
2900
2901static void U_CALLCONV
2902ures_loc_resetLocales(UEnumeration* en,
2903 UErrorCode* /*status*/) {
2904 UResourceBundle *res = &((ULocalesContext *)en->context)->installed;
2905 ures_resetIterator(res);
2906}
2907
Jungshik Shin5feb9ad2016-10-21 12:52:48 -07002908U_CDECL_END
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002909
2910static const UEnumeration gLocalesEnum = {
2911 NULL,
2912 NULL,
2913 ures_loc_closeLocales,
2914 ures_loc_countLocales,
2915 uenum_unextDefault,
2916 ures_loc_nextLocale,
2917 ures_loc_resetLocales
2918};
2919
2920
2921U_CAPI UEnumeration* U_EXPORT2
2922ures_openAvailableLocales(const char *path, UErrorCode *status)
2923{
2924 UResourceBundle *idx = NULL;
2925 UEnumeration *en = NULL;
2926 ULocalesContext *myContext = NULL;
2927
2928 if(U_FAILURE(*status)) {
2929 return NULL;
2930 }
2931 myContext = static_cast<ULocalesContext *>(uprv_malloc(sizeof(ULocalesContext)));
2932 en = (UEnumeration *)uprv_malloc(sizeof(UEnumeration));
2933 if(!en || !myContext) {
2934 *status = U_MEMORY_ALLOCATION_ERROR;
2935 uprv_free(en);
2936 uprv_free(myContext);
2937 return NULL;
2938 }
2939 uprv_memcpy(en, &gLocalesEnum, sizeof(UEnumeration));
2940
2941 ures_initStackObject(&myContext->installed);
2942 ures_initStackObject(&myContext->curr);
2943 idx = ures_openDirect(path, INDEX_LOCALE_NAME, status);
2944 ures_getByKey(idx, INDEX_TAG, &myContext->installed, status);
2945 if(U_SUCCESS(*status)) {
2946#if defined(URES_TREE_DEBUG)
2947 fprintf(stderr, "Got %s::%s::[%s] : %s\n",
2948 path, INDEX_LOCALE_NAME, INDEX_TAG, ures_getKey(&myContext->installed));
2949#endif
2950 en->context = myContext;
2951 } else {
2952#if defined(URES_TREE_DEBUG)
2953 fprintf(stderr, "%s open failed - %s\n", path, u_errorName(*status));
2954#endif
2955 ures_close(&myContext->installed);
2956 uprv_free(myContext);
2957 uprv_free(en);
2958 en = NULL;
2959 }
2960
2961 ures_close(idx);
2962
2963 return en;
2964}
2965
2966static UBool isLocaleInList(UEnumeration *locEnum, const char *locToSearch, UErrorCode *status) {
2967 const char *loc;
2968 while ((loc = uenum_next(locEnum, NULL, status)) != NULL) {
2969 if (uprv_strcmp(loc, locToSearch) == 0) {
Frank Tang1f164ee2022-11-08 12:31:27 -08002970 return true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002971 }
2972 }
Frank Tang1f164ee2022-11-08 12:31:27 -08002973 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002974}
2975
2976U_CAPI int32_t U_EXPORT2
2977ures_getFunctionalEquivalent(char *result, int32_t resultCapacity,
2978 const char *path, const char *resName, const char *keyword, const char *locid,
2979 UBool *isAvailable, UBool omitDefault, UErrorCode *status)
2980{
2981 char kwVal[1024] = ""; /* value of keyword 'keyword' */
2982 char defVal[1024] = ""; /* default value for given locale */
2983 char defLoc[1024] = ""; /* default value for given locale */
2984 char base[1024] = ""; /* base locale */
Frank Tangf2223962020-04-27 18:25:29 -07002985 char found[1024] = "";
2986 char parent[1024] = "";
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00002987 char full[1024] = "";
2988 UResourceBundle bund1, bund2;
2989 UResourceBundle *res = NULL;
2990 UErrorCode subStatus = U_ZERO_ERROR;
2991 int32_t length = 0;
2992 if(U_FAILURE(*status)) return 0;
2993 uloc_getKeywordValue(locid, keyword, kwVal, 1024-1,&subStatus);
2994 if(!uprv_strcmp(kwVal, DEFAULT_TAG)) {
2995 kwVal[0]=0;
2996 }
2997 uloc_getBaseName(locid, base, 1024-1,&subStatus);
2998#if defined(URES_TREE_DEBUG)
2999 fprintf(stderr, "getFunctionalEquivalent: \"%s\" [%s=%s] in %s - %s\n",
3000 locid, keyword, kwVal, base, u_errorName(subStatus));
3001#endif
3002 ures_initStackObject(&bund1);
3003 ures_initStackObject(&bund2);
3004
3005
3006 uprv_strcpy(parent, base);
3007 uprv_strcpy(found, base);
3008
3009 if(isAvailable) {
3010 UEnumeration *locEnum = ures_openAvailableLocales(path, &subStatus);
Frank Tang1f164ee2022-11-08 12:31:27 -08003011 *isAvailable = true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003012 if (U_SUCCESS(subStatus)) {
3013 *isAvailable = isLocaleInList(locEnum, parent, &subStatus);
3014 }
3015 uenum_close(locEnum);
3016 }
3017
3018 if(U_FAILURE(subStatus)) {
3019 *status = subStatus;
3020 return 0;
3021 }
3022
3023 do {
3024 subStatus = U_ZERO_ERROR;
3025 res = ures_open(path, parent, &subStatus);
3026 if(((subStatus == U_USING_FALLBACK_WARNING) ||
3027 (subStatus == U_USING_DEFAULT_WARNING)) && isAvailable)
3028 {
Frank Tang1f164ee2022-11-08 12:31:27 -08003029 *isAvailable = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003030 }
3031 isAvailable = NULL; /* only want to set this the first time around */
3032
3033#if defined(URES_TREE_DEBUG)
3034 fprintf(stderr, "%s;%s -> %s [%s]\n", path?path:"ICUDATA", parent, u_errorName(subStatus), ures_getLocale(res, &subStatus));
3035#endif
3036 if(U_FAILURE(subStatus)) {
3037 *status = subStatus;
3038 } else if(subStatus == U_ZERO_ERROR) {
3039 ures_getByKey(res,resName,&bund1, &subStatus);
3040 if(subStatus == U_ZERO_ERROR) {
3041 const UChar *defUstr;
3042 int32_t defLen;
3043 /* look for default item */
3044#if defined(URES_TREE_DEBUG)
3045 fprintf(stderr, "%s;%s : loaded default -> %s\n",
3046 path?path:"ICUDATA", parent, u_errorName(subStatus));
3047#endif
3048 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3049 if(U_SUCCESS(subStatus) && defLen) {
3050 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3051#if defined(URES_TREE_DEBUG)
3052 fprintf(stderr, "%s;%s -> default %s=%s, %s\n",
3053 path?path:"ICUDATA", parent, keyword, defVal, u_errorName(subStatus));
3054#endif
3055 uprv_strcpy(defLoc, parent);
3056 if(kwVal[0]==0) {
3057 uprv_strcpy(kwVal, defVal);
3058#if defined(URES_TREE_DEBUG)
3059 fprintf(stderr, "%s;%s -> kwVal = %s\n",
3060 path?path:"ICUDATA", parent, keyword, kwVal);
3061#endif
3062 }
3063 }
3064 }
3065 }
3066
3067 subStatus = U_ZERO_ERROR;
3068
3069 if (res != NULL) {
3070 uprv_strcpy(found, ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus));
3071 }
3072
3073 uloc_getParent(found,parent,sizeof(parent),&subStatus);
3074 ures_close(res);
3075 } while(!defVal[0] && *found && uprv_strcmp(found, "root") != 0 && U_SUCCESS(*status));
3076
3077 /* Now, see if we can find the kwVal collator.. start the search over.. */
3078 uprv_strcpy(parent, base);
3079 uprv_strcpy(found, base);
3080
3081 do {
3082 subStatus = U_ZERO_ERROR;
3083 res = ures_open(path, parent, &subStatus);
3084 if((subStatus == U_USING_FALLBACK_WARNING) && isAvailable) {
Frank Tang1f164ee2022-11-08 12:31:27 -08003085 *isAvailable = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003086 }
3087 isAvailable = NULL; /* only want to set this the first time around */
3088
3089#if defined(URES_TREE_DEBUG)
3090 fprintf(stderr, "%s;%s -> %s (looking for %s)\n",
3091 path?path:"ICUDATA", parent, u_errorName(subStatus), kwVal);
3092#endif
3093 if(U_FAILURE(subStatus)) {
3094 *status = subStatus;
3095 } else if(subStatus == U_ZERO_ERROR) {
3096 ures_getByKey(res,resName,&bund1, &subStatus);
3097#if defined(URES_TREE_DEBUG)
3098/**/ fprintf(stderr,"@%d [%s] %s\n", __LINE__, resName, u_errorName(subStatus));
3099#endif
3100 if(subStatus == U_ZERO_ERROR) {
3101 ures_getByKey(&bund1, kwVal, &bund2, &subStatus);
3102#if defined(URES_TREE_DEBUG)
3103/**/ fprintf(stderr,"@%d [%s] %s\n", __LINE__, kwVal, u_errorName(subStatus));
3104#endif
3105 if(subStatus == U_ZERO_ERROR) {
3106#if defined(URES_TREE_DEBUG)
3107 fprintf(stderr, "%s;%s -> full0 %s=%s, %s\n",
3108 path?path:"ICUDATA", parent, keyword, kwVal, u_errorName(subStatus));
3109#endif
3110 uprv_strcpy(full, parent);
3111 if(*full == 0) {
3112 uprv_strcpy(full, "root");
3113 }
3114 /* now, recalculate default kw if need be */
3115 if(uprv_strlen(defLoc) > uprv_strlen(full)) {
3116 const UChar *defUstr;
3117 int32_t defLen;
3118 /* look for default item */
3119#if defined(URES_TREE_DEBUG)
3120 fprintf(stderr, "%s;%s -> recalculating Default0\n",
3121 path?path:"ICUDATA", full);
3122#endif
3123 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3124 if(U_SUCCESS(subStatus) && defLen) {
3125 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3126#if defined(URES_TREE_DEBUG)
3127 fprintf(stderr, "%s;%s -> default0 %s=%s, %s\n",
3128 path?path:"ICUDATA", full, keyword, defVal, u_errorName(subStatus));
3129#endif
3130 uprv_strcpy(defLoc, full);
3131 }
3132 } /* end of recalculate default KW */
3133#if defined(URES_TREE_DEBUG)
3134 else {
3135 fprintf(stderr, "No trim0, %s <= %s\n", defLoc, full);
3136 }
3137#endif
3138 } else {
3139#if defined(URES_TREE_DEBUG)
3140 fprintf(stderr, "err=%s in %s looking for %s\n",
3141 u_errorName(subStatus), parent, kwVal);
3142#endif
3143 }
3144 }
3145 }
3146
3147 subStatus = U_ZERO_ERROR;
3148
3149 uprv_strcpy(found, parent);
3150 uloc_getParent(found,parent,1023,&subStatus);
3151 ures_close(res);
3152 } while(!full[0] && *found && U_SUCCESS(*status));
3153
3154 if((full[0]==0) && uprv_strcmp(kwVal, defVal)) {
3155#if defined(URES_TREE_DEBUG)
3156 fprintf(stderr, "Failed to locate kw %s - try default %s\n", kwVal, defVal);
3157#endif
3158 uprv_strcpy(kwVal, defVal);
3159 uprv_strcpy(parent, base);
3160 uprv_strcpy(found, base);
3161
3162 do { /* search for 'default' named item */
3163 subStatus = U_ZERO_ERROR;
3164 res = ures_open(path, parent, &subStatus);
3165 if((subStatus == U_USING_FALLBACK_WARNING) && isAvailable) {
Frank Tang1f164ee2022-11-08 12:31:27 -08003166 *isAvailable = false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003167 }
3168 isAvailable = NULL; /* only want to set this the first time around */
3169
3170#if defined(URES_TREE_DEBUG)
3171 fprintf(stderr, "%s;%s -> %s (looking for default %s)\n",
3172 path?path:"ICUDATA", parent, u_errorName(subStatus), kwVal);
3173#endif
3174 if(U_FAILURE(subStatus)) {
3175 *status = subStatus;
3176 } else if(subStatus == U_ZERO_ERROR) {
3177 ures_getByKey(res,resName,&bund1, &subStatus);
3178 if(subStatus == U_ZERO_ERROR) {
3179 ures_getByKey(&bund1, kwVal, &bund2, &subStatus);
3180 if(subStatus == U_ZERO_ERROR) {
3181#if defined(URES_TREE_DEBUG)
3182 fprintf(stderr, "%s;%s -> full1 %s=%s, %s\n", path?path:"ICUDATA",
3183 parent, keyword, kwVal, u_errorName(subStatus));
3184#endif
3185 uprv_strcpy(full, parent);
3186 if(*full == 0) {
3187 uprv_strcpy(full, "root");
3188 }
3189
3190 /* now, recalculate default kw if need be */
3191 if(uprv_strlen(defLoc) > uprv_strlen(full)) {
3192 const UChar *defUstr;
3193 int32_t defLen;
3194 /* look for default item */
3195#if defined(URES_TREE_DEBUG)
3196 fprintf(stderr, "%s;%s -> recalculating Default1\n",
3197 path?path:"ICUDATA", full);
3198#endif
3199 defUstr = ures_getStringByKey(&bund1, DEFAULT_TAG, &defLen, &subStatus);
3200 if(U_SUCCESS(subStatus) && defLen) {
3201 u_UCharsToChars(defUstr, defVal, u_strlen(defUstr));
3202#if defined(URES_TREE_DEBUG)
3203 fprintf(stderr, "%s;%s -> default %s=%s, %s\n",
3204 path?path:"ICUDATA", full, keyword, defVal, u_errorName(subStatus));
3205#endif
3206 uprv_strcpy(defLoc, full);
3207 }
3208 } /* end of recalculate default KW */
3209#if defined(URES_TREE_DEBUG)
3210 else {
3211 fprintf(stderr, "No trim1, %s <= %s\n", defLoc, full);
3212 }
3213#endif
3214 }
3215 }
3216 }
3217 subStatus = U_ZERO_ERROR;
3218
3219 uprv_strcpy(found, parent);
3220 uloc_getParent(found,parent,1023,&subStatus);
3221 ures_close(res);
3222 } while(!full[0] && *found && U_SUCCESS(*status));
3223 }
3224
3225 if(U_SUCCESS(*status)) {
3226 if(!full[0]) {
3227#if defined(URES_TREE_DEBUG)
3228 fprintf(stderr, "Still could not load keyword %s=%s\n", keyword, kwVal);
3229#endif
3230 *status = U_MISSING_RESOURCE_ERROR;
3231 } else if(omitDefault) {
3232#if defined(URES_TREE_DEBUG)
3233 fprintf(stderr,"Trim? full=%s, defLoc=%s, found=%s\n", full, defLoc, found);
3234#endif
3235 if(uprv_strlen(defLoc) <= uprv_strlen(full)) {
3236 /* found the keyword in a *child* of where the default tag was present. */
3237 if(!uprv_strcmp(kwVal, defVal)) { /* if the requested kw is default, */
3238 /* and the default is in or in an ancestor of the current locale */
3239#if defined(URES_TREE_DEBUG)
3240 fprintf(stderr, "Removing unneeded var %s=%s\n", keyword, kwVal);
3241#endif
3242 kwVal[0]=0;
3243 }
3244 }
3245 }
3246 uprv_strcpy(found, full);
3247 if(kwVal[0]) {
3248 uprv_strcat(found, "@");
3249 uprv_strcat(found, keyword);
3250 uprv_strcat(found, "=");
3251 uprv_strcat(found, kwVal);
3252 } else if(!omitDefault) {
3253 uprv_strcat(found, "@");
3254 uprv_strcat(found, keyword);
3255 uprv_strcat(found, "=");
3256 uprv_strcat(found, defVal);
3257 }
3258 }
3259 /* we found the default locale - no need to repeat it.*/
3260
3261 ures_close(&bund1);
3262 ures_close(&bund2);
3263
3264 length = (int32_t)uprv_strlen(found);
3265
3266 if(U_SUCCESS(*status)) {
3267 int32_t copyLength = uprv_min(length, resultCapacity);
3268 if(copyLength>0) {
3269 uprv_strncpy(result, found, copyLength);
3270 }
3271 if(length == 0) {
3272 *status = U_MISSING_RESOURCE_ERROR;
3273 }
3274 } else {
3275 length = 0;
3276 result[0]=0;
3277 }
3278 return u_terminateChars(result, resultCapacity, length, status);
3279}
3280
3281U_CAPI UEnumeration* U_EXPORT2
3282ures_getKeywordValues(const char *path, const char *keyword, UErrorCode *status)
3283{
3284#define VALUES_BUF_SIZE 2048
3285#define VALUES_LIST_SIZE 512
3286
3287 char valuesBuf[VALUES_BUF_SIZE];
3288 int32_t valuesIndex = 0;
3289 const char *valuesList[VALUES_LIST_SIZE];
3290 int32_t valuesCount = 0;
3291
3292 const char *locale;
3293 int32_t locLen;
3294
3295 UEnumeration *locs = NULL;
3296
3297 UResourceBundle item;
3298 UResourceBundle subItem;
3299
3300 ures_initStackObject(&item);
3301 ures_initStackObject(&subItem);
3302 locs = ures_openAvailableLocales(path, status);
3303
3304 if(U_FAILURE(*status)) {
3305 ures_close(&item);
3306 ures_close(&subItem);
3307 return NULL;
3308 }
3309
3310 valuesBuf[0]=0;
3311 valuesBuf[1]=0;
3312
Jungshik Shin42d50272018-10-24 01:22:09 -07003313 while((locale = uenum_next(locs, &locLen, status)) != 0) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003314 UResourceBundle *bund = NULL;
3315 UResourceBundle *subPtr = NULL;
3316 UErrorCode subStatus = U_ZERO_ERROR; /* don't fail if a bundle is unopenable */
Frank Tang516100f2021-07-14 12:36:44 -07003317 bund = ures_open(path, locale, &subStatus);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003318
3319#if defined(URES_TREE_DEBUG)
3320 if(!bund || U_FAILURE(subStatus)) {
3321 fprintf(stderr, "%s-%s values: Can't open %s locale - skipping. (%s)\n",
3322 path?path:"<ICUDATA>", keyword, locale, u_errorName(subStatus));
3323 }
3324#endif
3325
3326 ures_getByKey(bund, keyword, &item, &subStatus);
3327
3328 if(!bund || U_FAILURE(subStatus)) {
3329#if defined(URES_TREE_DEBUG)
3330 fprintf(stderr, "%s-%s values: Can't find in %s - skipping. (%s)\n",
3331 path?path:"<ICUDATA>", keyword, locale, u_errorName(subStatus));
3332#endif
3333 ures_close(bund);
3334 bund = NULL;
3335 continue;
3336 }
3337
Jungshik Shin42d50272018-10-24 01:22:09 -07003338 while((subPtr = ures_getNextResource(&item,&subItem,&subStatus)) != 0
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003339 && U_SUCCESS(subStatus)) {
3340 const char *k;
3341 int32_t i;
3342 k = ures_getKey(subPtr);
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08003343
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003344#if defined(URES_TREE_DEBUG)
3345 /* fprintf(stderr, "%s | %s | %s | %s\n", path?path:"<ICUDATA>", keyword, locale, k); */
3346#endif
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08003347 if(k == NULL || *k == 0 ||
3348 uprv_strcmp(k, DEFAULT_TAG) == 0 || uprv_strncmp(k, "private-", 8) == 0) {
3349 // empty or "default" or unlisted type
3350 continue;
3351 }
3352 for(i=0; i<valuesCount; i++) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003353 if(!uprv_strcmp(valuesList[i],k)) {
3354 k = NULL; /* found duplicate */
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08003355 break;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003356 }
3357 }
Jungshik Shin (jungshik at google)0f8746a2015-01-08 15:46:45 -08003358 if(k != NULL) {
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003359 int32_t kLen = (int32_t)uprv_strlen(k);
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003360 if((valuesCount >= (VALUES_LIST_SIZE-1)) || /* no more space in list .. */
3361 ((valuesIndex+kLen+1+1) >= VALUES_BUF_SIZE)) { /* no more space in buffer (string + 2 nulls) */
3362 *status = U_ILLEGAL_ARGUMENT_ERROR; /* out of space.. */
3363 } else {
3364 uprv_strcpy(valuesBuf+valuesIndex, k);
3365 valuesList[valuesCount++] = valuesBuf+valuesIndex;
3366 valuesIndex += kLen;
3367#if defined(URES_TREE_DEBUG)
3368 fprintf(stderr, "%s | %s | %s | [%s] (UNIQUE)\n",
3369 path?path:"<ICUDATA>", keyword, locale, k);
3370#endif
3371 valuesBuf[valuesIndex++] = 0; /* terminate */
3372 }
3373 }
3374 }
3375 ures_close(bund);
3376 }
3377 valuesBuf[valuesIndex++] = 0; /* terminate */
3378
3379 ures_close(&item);
3380 ures_close(&subItem);
3381 uenum_close(locs);
3382#if defined(URES_TREE_DEBUG)
3383 fprintf(stderr, "%s: size %d, #%d\n", u_errorName(*status),
3384 valuesIndex, valuesCount);
3385#endif
3386 return uloc_openKeywordList(valuesBuf, valuesIndex, status);
3387}
3388#if 0
3389/* This code isn't needed, and given the documentation warnings the implementation is suspect */
Frank Tangf90543d2020-10-30 19:02:04 -07003390U_CAPI UBool U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003391ures_equal(const UResourceBundle* res1, const UResourceBundle* res2){
3392 if(res1==NULL || res2==NULL){
Frank Tang7e7574b2021-04-13 21:19:13 -07003393 return res1==res2; /* pointer comparison */
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003394 }
3395 if(res1->fKey==NULL|| res2->fKey==NULL){
3396 return (res1->fKey==res2->fKey);
3397 }else{
3398 if(uprv_strcmp(res1->fKey, res2->fKey)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003399 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003400 }
3401 }
3402 if(uprv_strcmp(res1->fData->fName, res2->fData->fName)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003403 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003404 }
3405 if(res1->fData->fPath == NULL|| res2->fData->fPath==NULL){
3406 return (res1->fData->fPath == res2->fData->fPath);
3407 }else{
3408 if(uprv_strcmp(res1->fData->fPath, res2->fData->fPath)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003409 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003410 }
3411 }
3412 if(uprv_strcmp(res1->fData->fParent->fName, res2->fData->fParent->fName)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003413 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003414 }
3415 if(uprv_strcmp(res1->fData->fParent->fPath, res2->fData->fParent->fPath)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003416 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003417 }
3418 if(uprv_strncmp(res1->fResPath, res2->fResPath, res1->fResPathLen)!=0){
Frank Tang1f164ee2022-11-08 12:31:27 -08003419 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003420 }
3421 if(res1->fRes != res2->fRes){
Frank Tang1f164ee2022-11-08 12:31:27 -08003422 return false;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003423 }
Frank Tang1f164ee2022-11-08 12:31:27 -08003424 return true;
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003425}
Frank Tangf90543d2020-10-30 19:02:04 -07003426U_CAPI UResourceBundle* U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003427ures_clone(const UResourceBundle* res, UErrorCode* status){
3428 UResourceBundle* bundle = NULL;
3429 UResourceBundle* ret = NULL;
3430 if(U_FAILURE(*status) || res == NULL){
3431 return NULL;
3432 }
3433 bundle = ures_open(res->fData->fPath, res->fData->fName, status);
3434 if(res->fResPath!=NULL){
3435 ret = ures_findSubResource(bundle, res->fResPath, NULL, status);
3436 ures_close(bundle);
3437 }else{
3438 ret = bundle;
3439 }
3440 return ret;
3441}
Frank Tangf90543d2020-10-30 19:02:04 -07003442U_CAPI const UResourceBundle* U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003443ures_getParentBundle(const UResourceBundle* res){
3444 if(res==NULL){
3445 return NULL;
3446 }
3447 return res->fParentRes;
3448}
3449#endif
3450
Frank Tangf90543d2020-10-30 19:02:04 -07003451U_CAPI void U_EXPORT2
jshin@chromium.org6f31ac32014-03-26 22:15:14 +00003452ures_getVersionByKey(const UResourceBundle* res, const char *key, UVersionInfo ver, UErrorCode *status) {
3453 const UChar *str;
3454 int32_t len;
3455 str = ures_getStringByKey(res, key, &len, status);
3456 if(U_SUCCESS(*status)) {
3457 u_versionFromUString(ver, str);
3458 }
3459}
3460
3461/* eof */