1 /*
2 **********************************************************************
3 * Copyright (C) 2014, International Business Machines
4 * Corporation and others. All Rights Reserved.
5 **********************************************************************
6 *
7 * Created 2014-06-20 by Steven R. Loomis
8 *
9 * See: http://bugs.icu-project.org/trac/ticket/10922
10 *
11 */
12
13 /*
14 WHAT IS THIS?
15
16 Here's the problem: It's difficult to reconfigure ICU from the command
17 line without using the full makefiles. You can do a lot, but not
18 everything.
19
20 Consider:
21
22 $ icupkg -r 'ja*' icudt53l.dat
23
24 Great, you've now removed the (main) Japanese data. But something's
25 still wrong-- res_index (and thus, getAvailable* functions) still
26 claim the locale is present.
27
28 You are reading the source to a tool (using only public API C code)
29 that can solve this problem. Use as follows:
30
31 $ iculslocs -i . -N icudt53l -b res_index.txt
32
33 .. Generates a NEW res_index.txt (by looking at the .dat file, and
34 figuring out which locales are actually available. Has commented out
35 the ones which are no longer available:
36
37 ...
38 it_SM {""}
39 // ja {""}
40 // ja_JP {""}
41 jgo {""}
42 ...
43
44 Then you can build and in-place patch it with existing ICU tools:
45 $ genrb res_index.txt
46 $ icupkg -a res_index.res icudt53l.dat
47
48 .. Now you have a patched icudt539.dat that not only doesn't have
49 Japanese, it doesn't *claim* to have Japanese.
50
51 */
52
53 #include <cstring>
54 #include "charstr.h" // ICU internal header
55 #include <unicode/ures.h>
56 #include <unicode/udata.h>
57 #include <unicode/putil.h>
58 #include <cstdio>
59
60 const char* PROG = "iculslocs";
61 const char* NAME = U_ICUDATA_NAME; // assume ICU data
62 const char* TREE = "ROOT";
63 int VERBOSE = 0;
64
65 #define RES_INDEX "res_index"
66 #define INSTALLEDLOCALES "InstalledLocales"
67
68 icu::CharString packageName;
69 const char* locale = RES_INDEX; // locale referring to our index
70
usage()71 void usage() {
72 printf("Usage: %s [options]\n", PROG);
73 printf(
74 "This program lists and optionally regenerates the locale "
75 "manifests\n"
76 " in ICU 'res_index.res' files.\n");
77 printf(
78 " -i ICUDATA Set ICUDATA dir to ICUDATA.\n"
79 " NOTE: this must be the first option given.\n");
80 printf(" -h This Help\n");
81 printf(" -v Verbose Mode on\n");
82 printf(" -l List locales to stdout\n");
83 printf(
84 " if Verbose mode, then missing (unopenable)"
85 "locales\n"
86 " will be listed preceded by a '#'.\n");
87 printf(
88 " -b res_index.txt Write 'corrected' bundle "
89 "to res_index.txt\n"
90 " missing bundles will be "
91 "OMITTED\n");
92 printf(
93 " -T TREE Choose tree TREE\n"
94 " (TREE should be one of: \n"
95 " ROOT, brkitr, coll, curr, lang, rbnf, region, zone)\n");
96 // see ureslocs.h and elsewhere
97 printf(
98 " -N NAME Choose name NAME\n"
99 " (default: '%s')\n",
100 U_ICUDATA_NAME);
101 printf(
102 "\nNOTE: for best results, this tool ought to be "
103 "linked against\n"
104 "stubdata. i.e. '%s -l' SHOULD return an error with "
105 " no data.\n",
106 PROG);
107 }
108
109 #define ASSERT_SUCCESS(status, what) \
110 if (U_FAILURE(*status)) { \
111 printf("%s:%d: %s: ERROR: %s %s\n", \
112 __FILE__, \
113 __LINE__, \
114 PROG, \
115 u_errorName(*status), \
116 what); \
117 return 1; \
118 }
119
120 /**
121 * @param status changed from reference to pointer to match node.js style
122 */
calculatePackageName(UErrorCode * status)123 void calculatePackageName(UErrorCode* status) {
124 packageName.clear();
125 if (strcmp(NAME, "NONE")) {
126 packageName.append(NAME, *status);
127 if (strcmp(TREE, "ROOT")) {
128 packageName.append(U_TREE_SEPARATOR_STRING, *status);
129 packageName.append(TREE, *status);
130 }
131 }
132 if (VERBOSE) {
133 printf("packageName: %s\n", packageName.data());
134 }
135 }
136
137 /**
138 * Does the locale exist?
139 * return zero for false, or nonzero if it was openable.
140 * Assumes calculatePackageName was called.
141 * @param exists set to TRUE if exists, FALSE otherwise.
142 * Changed from reference to pointer to match node.js style
143 * @return 0 on "OK" (success or resource-missing),
144 * 1 on "FAILURE" (unexpected error)
145 */
localeExists(const char * loc,UBool * exists)146 int localeExists(const char* loc, UBool* exists) {
147 UErrorCode status = U_ZERO_ERROR;
148 if (VERBOSE > 1) {
149 printf("Trying to open %s:%s\n", packageName.data(), loc);
150 }
151 icu::LocalUResourceBundlePointer aResource(
152 ures_openDirect(packageName.data(), loc, &status));
153 *exists = false;
154 if (U_SUCCESS(status)) {
155 *exists = true;
156 if (VERBOSE > 1) {
157 printf("%s:%s existed!\n", packageName.data(), loc);
158 }
159 return 0;
160 } else if (status == U_MISSING_RESOURCE_ERROR) {
161 *exists = false;
162 if (VERBOSE > 1) {
163 printf("%s:%s did NOT exist (%s)!\n",
164 packageName.data(),
165 loc,
166 u_errorName(status));
167 }
168 return 0; // "good" failure
169 } else {
170 // some other failure..
171 printf("%s:%d: %s: ERROR %s opening %s for test.\n",
172 __FILE__,
173 __LINE__,
174 u_errorName(status),
175 packageName.data(),
176 loc);
177 return 1; // abort
178 }
179 }
180
printIndent(FILE * bf,int indent)181 void printIndent(FILE* bf, int indent) {
182 for (int i = 0; i < indent + 1; i++) {
183 fprintf(bf, " ");
184 }
185 }
186
187 /**
188 * Dumps a table resource contents
189 * if lev==0, skips INSTALLEDLOCALES
190 * @return 0 for OK, 1 for err
191 */
dumpAllButInstalledLocales(int lev,icu::LocalUResourceBundlePointer * bund,FILE * bf,UErrorCode * status)192 int dumpAllButInstalledLocales(int lev,
193 icu::LocalUResourceBundlePointer* bund,
194 FILE* bf,
195 UErrorCode* status) {
196 ures_resetIterator(bund->getAlias());
197 icu::LocalUResourceBundlePointer t;
198 while (U_SUCCESS(*status) && ures_hasNext(bund->getAlias())) {
199 t.adoptInstead(ures_getNextResource(bund->getAlias(), t.orphan(), status));
200 ASSERT_SUCCESS(status, "while processing table");
201 const char* key = ures_getKey(t.getAlias());
202 if (VERBOSE > 1) {
203 printf("dump@%d: got key %s\n", lev, key);
204 }
205 if (lev == 0 && !strcmp(key, INSTALLEDLOCALES)) {
206 if (VERBOSE > 1) {
207 printf("dump: skipping '%s' as it must be evaluated.\n", key);
208 }
209 } else {
210 printIndent(bf, lev);
211 fprintf(bf, "%s", key);
212 const UResType type = ures_getType(t.getAlias());
213 switch (type) {
214 case URES_STRING: {
215 int32_t len = 0;
216 const UChar* s = ures_getString(t.getAlias(), &len, status);
217 ASSERT_SUCCESS(status, "getting string");
218 fprintf(bf, ":string {\"");
219 fwrite(s, len, 1, bf);
220 fprintf(bf, "\"}");
221 } break;
222 case URES_TABLE: {
223 fprintf(bf, ":table {\n");
224 dumpAllButInstalledLocales(lev+1, &t, bf, status);
225 printIndent(bf, lev);
226 fprintf(bf, "}\n");
227 } break;
228 default: {
229 printf("ERROR: unhandled type %d for key %s "
230 "in dumpAllButInstalledLocales().\n",
231 static_cast<int>(type), key);
232 return 1;
233 } break;
234 }
235 fprintf(bf, "\n");
236 }
237 }
238 return 0;
239 }
240
list(const char * toBundle)241 int list(const char* toBundle) {
242 UErrorCode status = U_ZERO_ERROR;
243
244 FILE* bf = nullptr;
245
246 if (toBundle != nullptr) {
247 if (VERBOSE) {
248 printf("writing to bundle %s\n", toBundle);
249 }
250 bf = fopen(toBundle, "wb");
251 if (bf == nullptr) {
252 printf("ERROR: Could not open '%s' for writing.\n", toBundle);
253 return 1;
254 }
255 fprintf(bf, "\xEF\xBB\xBF"); // write UTF-8 BOM
256 fprintf(bf, "// -*- Coding: utf-8; -*-\n//\n");
257 }
258
259 // first, calculate the bundle name.
260 calculatePackageName(&status);
261 ASSERT_SUCCESS(&status, "calculating package name");
262
263 if (VERBOSE) {
264 printf("\"locale\": %s\n", locale);
265 }
266
267 icu::LocalUResourceBundlePointer bund(
268 ures_openDirect(packageName.data(), locale, &status));
269 ASSERT_SUCCESS(&status, "while opening the bundle");
270 icu::LocalUResourceBundlePointer installedLocales(
271 // NOLINTNEXTLINE (readability/null_usage)
272 ures_getByKey(bund.getAlias(), INSTALLEDLOCALES, nullptr, &status));
273 ASSERT_SUCCESS(&status, "while fetching installed locales");
274
275 int32_t count = ures_getSize(installedLocales.getAlias());
276 if (VERBOSE) {
277 printf("Locales: %d\n", count);
278 }
279
280 if (bf != nullptr) {
281 // write the HEADER
282 fprintf(bf,
283 "// NOTE: This file was generated during the build process.\n"
284 "// Generator: tools/icu/iculslocs.cc\n"
285 "// Input package-tree/item: %s/%s.res\n",
286 packageName.data(),
287 locale);
288 fprintf(bf,
289 "%s:table(nofallback) {\n"
290 " // First, everything besides InstalledLocales:\n",
291 locale);
292 if (dumpAllButInstalledLocales(0, &bund, bf, &status)) {
293 printf("Error dumping prolog for %s\n", toBundle);
294 fclose(bf);
295 return 1;
296 }
297 // in case an error was missed
298 ASSERT_SUCCESS(&status, "while writing prolog");
299
300 fprintf(bf,
301 " %s:table { // %d locales in input %s.res\n",
302 INSTALLEDLOCALES,
303 count,
304 locale);
305 }
306
307 // OK, now list them.
308 icu::LocalUResourceBundlePointer subkey;
309
310 int validCount = 0;
311 for (int32_t i = 0; i < count; i++) {
312 subkey.adoptInstead(ures_getByIndex(
313 installedLocales.getAlias(), i, subkey.orphan(), &status));
314 ASSERT_SUCCESS(&status, "while fetching an installed locale's name");
315
316 const char* key = ures_getKey(subkey.getAlias());
317 if (VERBOSE > 1) {
318 printf("@%d: %s\n", i, key);
319 }
320 // now, see if the locale is installed..
321
322 UBool exists;
323 if (localeExists(key, &exists)) {
324 if (bf != nullptr) fclose(bf);
325 return 1; // get out.
326 }
327 if (exists) {
328 validCount++;
329 printf("%s\n", key);
330 if (bf != nullptr) {
331 fprintf(bf, " %s {\"\"}\n", key);
332 }
333 } else {
334 if (bf != nullptr) {
335 fprintf(bf, "// %s {\"\"}\n", key);
336 }
337 if (VERBOSE) {
338 printf("#%s\n", key); // verbosity one - '' vs '#'
339 }
340 }
341 }
342
343 if (bf != nullptr) {
344 fprintf(bf, " } // %d/%d valid\n", validCount, count);
345 // write the HEADER
346 fprintf(bf, "}\n");
347 fclose(bf);
348 }
349
350 return 0;
351 }
352
main(int argc,const char * argv[])353 int main(int argc, const char* argv[]) {
354 PROG = argv[0];
355 for (int i = 1; i < argc; i++) {
356 const char* arg = argv[i];
357 int argsLeft = argc - i - 1; /* how many remain? */
358 if (!strcmp(arg, "-v")) {
359 VERBOSE++;
360 } else if (!strcmp(arg, "-i") && (argsLeft >= 1)) {
361 if (i != 1) {
362 printf("ERROR: -i must be the first argument given.\n");
363 usage();
364 return 1;
365 }
366 const char* dir = argv[++i];
367 u_setDataDirectory(dir);
368 if (VERBOSE) {
369 printf("ICUDATA is now %s\n", dir);
370 }
371 } else if (!strcmp(arg, "-T") && (argsLeft >= 1)) {
372 TREE = argv[++i];
373 if (VERBOSE) {
374 printf("TREE is now %s\n", TREE);
375 }
376 } else if (!strcmp(arg, "-N") && (argsLeft >= 1)) {
377 NAME = argv[++i];
378 if (VERBOSE) {
379 printf("NAME is now %s\n", NAME);
380 }
381 } else if (!strcmp(arg, "-?") || !strcmp(arg, "-h")) {
382 usage();
383 return 0;
384 } else if (!strcmp(arg, "-l")) {
385 if (list(nullptr)) {
386 return 1;
387 }
388 } else if (!strcmp(arg, "-b") && (argsLeft >= 1)) {
389 if (list(argv[++i])) {
390 return 1;
391 }
392 } else {
393 printf("Unknown or malformed option: %s\n", arg);
394 usage();
395 return 1;
396 }
397 }
398 }
399
400 // Local Variables:
401 // compile-command: "icurun iculslocs.cpp"
402 // End:
403