• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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