1 /*
2 * Copyright (C) 2002-2006 Sergey V. Udaltsov <svu@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 */
19
20 #include <errno.h>
21 #include <locale.h>
22 #include <libintl.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <sys/param.h>
26 #include <sys/stat.h>
27
28 #include "config.h"
29
30 #include "xklavier_private.h"
31
32 #define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
33 #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
34
35 static GHashTable *country_code_names = NULL;
36 static GHashTable *lang_code_names = NULL;
37
38 typedef struct {
39 const gchar *domain;
40 const gchar **attr_names;
41 } LookupParams;
42
43 typedef struct {
44 GHashTable *code_names;
45 const gchar *tag_name;
46 LookupParams *params;
47 } CodeBuildStruct;
48
49 static const char *countryLookupNames[] = { "alpha_2_code", NULL };
50 static const char *languageLookupNames[] =
51 { "iso_639_2B_code", "iso_639_2T_code", NULL };
52
53 static LookupParams countryLookup = { "iso_3166", countryLookupNames };
54 static LookupParams languageLookup = { "iso_639", languageLookupNames };
55
56 static void
iso_codes_parse_start_tag(GMarkupParseContext * ctx,const gchar * element_name,const gchar ** attr_names,const gchar ** attr_values,gpointer user_data,GError ** error)57 iso_codes_parse_start_tag(GMarkupParseContext * ctx,
58 const gchar * element_name,
59 const gchar ** attr_names,
60 const gchar ** attr_values,
61 gpointer user_data, GError ** error)
62 {
63 const gchar *name;
64 const gchar **san = attr_names, **sav = attr_values;
65 CodeBuildStruct *cbs = (CodeBuildStruct *) user_data;
66
67 /* Is this the tag we are looking for? */
68 if (!g_str_equal(element_name, cbs->tag_name) ||
69 attr_names == NULL || attr_values == NULL) {
70 return;
71 }
72
73 name = NULL;
74
75 /* What would be the value? */
76 while (*attr_names && *attr_values) {
77 if (g_str_equal(*attr_names, "name")) {
78 name = *attr_values;
79 break;
80 }
81
82 attr_names++;
83 attr_values++;
84 }
85
86 if (!name) {
87 return;
88 }
89
90 attr_names = san;
91 attr_values = sav;
92
93 /* Walk again the attributes */
94 while (*attr_names && *attr_values) {
95 const gchar **attr = cbs->params->attr_names;
96 /* Look through all the attributess we are interested in */
97 while (*attr) {
98 if (g_str_equal(*attr_names, *attr)) {
99 if (**attr_values) {
100 g_hash_table_insert
101 (cbs->code_names,
102 g_strdup(*attr_values),
103 g_strdup(name));
104 }
105 }
106 attr++;
107 }
108
109 attr_names++;
110 attr_values++;
111 }
112 }
113
114 static GHashTable *
iso_code_names_init(LookupParams * params)115 iso_code_names_init(LookupParams * params)
116 {
117 GError *err = NULL;
118 gchar *buf, *filename, *tag_name;
119 gsize buf_len;
120 CodeBuildStruct cbs;
121
122 GHashTable *ht = g_hash_table_new_full(g_str_hash, g_str_equal,
123 g_free, g_free);
124
125 tag_name = g_strdup_printf("%s_entry", params->domain);
126
127 cbs.code_names = ht;
128 cbs.tag_name = tag_name;
129 cbs.params = params;
130
131 bindtextdomain(params->domain, ISO_CODES_LOCALESDIR);
132 bind_textdomain_codeset(params->domain, "UTF-8");
133
134 filename =
135 g_strdup_printf("%s/%s.xml", ISO_CODES_DATADIR,
136 params->domain);
137 if (g_file_get_contents(filename, &buf, &buf_len, &err)) {
138 GMarkupParseContext *ctx;
139 GMarkupParser parser = {
140 iso_codes_parse_start_tag,
141 NULL, NULL, NULL, NULL
142 };
143
144 ctx = g_markup_parse_context_new(&parser, 0, &cbs, NULL);
145 if (!g_markup_parse_context_parse(ctx, buf, buf_len, &err)) {
146 g_warning("Failed to parse '%s/%s.xml': %s",
147 ISO_CODES_DATADIR,
148 params->domain, err->message);
149 g_error_free(err);
150 }
151
152 g_markup_parse_context_free(ctx);
153 g_free(buf);
154 } else {
155 g_warning("Failed to load '%s/%s.xml': %s",
156 ISO_CODES_DATADIR, params->domain, err->message);
157 g_error_free(err);
158 }
159 g_free(filename);
160 g_free(tag_name);
161
162 return ht;
163 }
164
165 typedef const gchar *(*DescriptionGetterFunc) (const gchar * code);
166
167 const gchar *
xkl_get_language_name(const gchar * code)168 xkl_get_language_name(const gchar * code)
169 {
170 const gchar *name;
171
172 if (!lang_code_names) {
173 lang_code_names = iso_code_names_init(&languageLookup);
174 }
175
176 name = g_hash_table_lookup(lang_code_names, code);
177 if (!name) {
178 return NULL;
179 }
180
181 return dgettext("iso_639", name);
182 }
183
184 const gchar *
xkl_get_country_name(const gchar * code)185 xkl_get_country_name(const gchar * code)
186 {
187 const gchar *name;
188
189 if (!country_code_names) {
190 country_code_names = iso_code_names_init(&countryLookup);
191 }
192
193 name = g_hash_table_lookup(country_code_names, code);
194 if (!name) {
195 return NULL;
196 }
197
198 return dgettext("iso_3166", name);
199 }
200
201 static void
xkl_config_registry_foreach_iso_code(XklConfigRegistry * config,XklConfigItemProcessFunc func,const gchar * xpath_exprs[],DescriptionGetterFunc dgf,gboolean to_upper,gpointer data)202 xkl_config_registry_foreach_iso_code(XklConfigRegistry * config,
203 XklConfigItemProcessFunc func,
204 const gchar * xpath_exprs[],
205 DescriptionGetterFunc dgf,
206 gboolean to_upper, gpointer data)
207 {
208 GHashTable *code_pairs;
209 GHashTableIter iter;
210 xmlXPathObjectPtr xpath_obj;
211 const gchar **xpath_expr;
212 gpointer key, value;
213 XklConfigItem *ci;
214 gint di;
215
216 if (!xkl_config_registry_is_initialized(config))
217 return;
218
219 code_pairs = g_hash_table_new(g_str_hash, g_str_equal);
220
221 for (xpath_expr = xpath_exprs; *xpath_expr; xpath_expr++) {
222 for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
223 gint ni;
224 xmlNodePtr *node;
225 xmlNodeSetPtr nodes;
226
227 xmlXPathContextPtr xmlctxt =
228 xkl_config_registry_priv(config,
229 xpath_contexts[di]);
230 if (xmlctxt == NULL)
231 continue;
232
233 xpath_obj =
234 xmlXPathEval((unsigned char *) *xpath_expr,
235 xmlctxt);
236 if (xpath_obj == NULL)
237 continue;
238
239 nodes = xpath_obj->nodesetval;
240 if (nodes == NULL) {
241 xmlXPathFreeObject(xpath_obj);
242 continue;
243 }
244
245 node = nodes->nodeTab;
246 for (ni = nodes->nodeNr; --ni >= 0;) {
247 gchar *iso_code =
248 (gchar *) (*node)->children->content;
249 const gchar *description;
250 iso_code =
251 to_upper ?
252 g_ascii_strup(iso_code,
253 -1) : g_strdup(iso_code);
254 description = dgf(iso_code);
255 /* If there is a mapping to some ISO description - consider it as ISO code (well, it is just an assumption) */
256 if (description)
257 g_hash_table_insert
258 (code_pairs,
259 g_strdup
260 (iso_code),
261 g_strdup(description));
262 g_free(iso_code);
263 node++;
264 }
265
266 xmlXPathFreeObject(xpath_obj);
267 }
268 }
269
270 g_hash_table_iter_init(&iter, code_pairs);
271 ci = xkl_config_item_new();
272 while (g_hash_table_iter_next(&iter, &key, &value)) {
273 g_strlcpy(ci->name, (const gchar *) key, sizeof(ci->name));
274 g_strlcpy(ci->description, (const gchar *) value,
275 sizeof(ci->description));
276 func(config, ci, data);
277 }
278 g_object_unref(G_OBJECT(ci));
279 g_hash_table_unref(code_pairs);
280 }
281
282 void
xkl_config_registry_foreach_country(XklConfigRegistry * config,XklConfigItemProcessFunc func,gpointer data)283 xkl_config_registry_foreach_country(XklConfigRegistry *
284 config,
285 XklConfigItemProcessFunc
286 func, gpointer data)
287 {
288 const gchar *xpath_exprs[] = {
289 XKBCR_LAYOUT_PATH "/configItem/countryList/iso3166Id",
290 XKBCR_LAYOUT_PATH "/configItem/name",
291 NULL
292 };
293
294 xkl_config_registry_foreach_iso_code(config, func, xpath_exprs,
295 xkl_get_country_name, TRUE,
296 data);
297 }
298
299 void
xkl_config_registry_foreach_language(XklConfigRegistry * config,XklConfigItemProcessFunc func,gpointer data)300 xkl_config_registry_foreach_language(XklConfigRegistry *
301 config,
302 XklConfigItemProcessFunc
303 func, gpointer data)
304 {
305 const gchar *xpath_exprs[] = {
306 XKBCR_LAYOUT_PATH "/configItem/languageList/iso639Id",
307 XKBCR_VARIANT_PATH "/configItem/languageList/iso639Id",
308 NULL
309 };
310
311 xkl_config_registry_foreach_iso_code(config, func, xpath_exprs,
312 xkl_get_language_name, FALSE,
313 data);
314 }
315
316 void
xkl_config_registry_foreach_iso_variant(XklConfigRegistry * config,const gchar * iso_code,XklTwoConfigItemsProcessFunc func,gpointer data,const gchar * layout_xpath_exprs[],const gboolean should_code_be_lowered1[],const gchar * variant_xpath_exprs[],const gboolean should_code_be_lowered2[])317 xkl_config_registry_foreach_iso_variant(XklConfigRegistry *
318 config,
319 const gchar *
320 iso_code,
321 XklTwoConfigItemsProcessFunc
322 func, gpointer data,
323 const gchar * layout_xpath_exprs[],
324 const gboolean
325 should_code_be_lowered1[],
326 const gchar *
327 variant_xpath_exprs[],
328 const gboolean
329 should_code_be_lowered2[])
330 {
331 xmlXPathObjectPtr xpath_obj;
332 xmlNodeSetPtr nodes;
333 const gchar **xpath_expr;
334 const gboolean *is_low_id = should_code_be_lowered1;
335 gchar *low_iso_code;
336
337 if (!xkl_config_registry_is_initialized(config))
338 return;
339
340 low_iso_code = g_ascii_strdown(iso_code, -1);
341
342 for (xpath_expr = layout_xpath_exprs; *xpath_expr;
343 xpath_expr++, is_low_id++) {
344 const gchar *aic = *is_low_id ? low_iso_code : iso_code;
345 gchar *xpe = g_strdup_printf(*xpath_expr, aic);
346 gint di;
347 GSList *processed_ids = NULL;
348
349 for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
350 xmlXPathContextPtr xmlctxt =
351 xkl_config_registry_priv(config,
352 xpath_contexts[di]);
353 if (xmlctxt == NULL)
354 continue;
355
356 xpath_obj =
357 xmlXPathEval((unsigned char *) xpe, xmlctxt);
358 if (xpath_obj == NULL)
359 continue;
360
361 nodes = xpath_obj->nodesetval;
362 if (nodes != NULL) {
363 gint ni;
364 xmlNodePtr *node = nodes->nodeTab;
365 XklConfigItem *ci = xkl_config_item_new();
366 for (ni = nodes->nodeNr; --ni >= 0;) {
367 if (xkl_read_config_item
368 (config, di, *node, ci)) {
369 if (g_slist_find_custom
370 (processed_ids,
371 ci->name,
372 (GCompareFunc)
373 g_ascii_strcasecmp) ==
374 NULL) {
375 func(config, ci,
376 NULL, data);
377 processed_ids =
378 g_slist_append
379 (processed_ids,
380 g_strdup
381 (ci->name));
382 }
383 }
384 node++;
385 }
386 g_object_unref(G_OBJECT(ci));
387 }
388 xmlXPathFreeObject(xpath_obj);
389 }
390 g_free(xpe);
391 }
392
393 is_low_id = should_code_be_lowered2;
394 for (xpath_expr = variant_xpath_exprs; *xpath_expr;
395 xpath_expr++, is_low_id++) {
396 const gchar *aic = *is_low_id ? low_iso_code : iso_code;
397 gchar *xpe = g_strdup_printf(*xpath_expr, aic);
398 gint di;
399 for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
400 xmlXPathContextPtr xmlctxt =
401 xkl_config_registry_priv(config,
402 xpath_contexts[di]);
403 if (xmlctxt == NULL)
404 continue;
405
406 xpath_obj =
407 xmlXPathEval((unsigned char *) xpe, xmlctxt);
408 if (xpath_obj == NULL)
409 continue;
410
411 nodes = xpath_obj->nodesetval;
412 if (nodes != NULL) {
413 gint ni;
414 xmlNodePtr *node = nodes->nodeTab;
415 XklConfigItem *ci = xkl_config_item_new();
416 XklConfigItem *pci = xkl_config_item_new();
417 for (ni = nodes->nodeNr; --ni >= 0;) {
418 if (xkl_read_config_item
419 (config, di, *node, ci) &&
420 xkl_read_config_item
421 (config, di,
422 (*node)->parent->parent, pci))
423 func(config, pci, ci,
424 data);
425 node++;
426 }
427 g_object_unref(G_OBJECT(pci));
428 g_object_unref(G_OBJECT(ci));
429 }
430 xmlXPathFreeObject(xpath_obj);
431 }
432 g_free(xpe);
433 }
434
435 g_free(low_iso_code);
436 }
437
438 void
xkl_config_registry_foreach_country_variant(XklConfigRegistry * config,const gchar * country_code,XklTwoConfigItemsProcessFunc func,gpointer data)439 xkl_config_registry_foreach_country_variant(XklConfigRegistry *
440 config,
441 const gchar *
442 country_code,
443 XklTwoConfigItemsProcessFunc
444 func, gpointer data)
445 {
446 const gchar *layout_xpath_exprs[] = {
447 XKBCR_LAYOUT_PATH "[configItem/name = '%s']",
448 XKBCR_LAYOUT_PATH
449 "[configItem/countryList/iso3166Id = '%s']",
450 NULL
451 };
452 const gboolean should_code_be_lowered1[] = { TRUE, FALSE };
453
454 const gchar *variant_xpath_exprs[] = {
455 XKBCR_VARIANT_PATH
456 "[configItem/countryList/iso3166Id = '%s']",
457 XKBCR_VARIANT_PATH
458 "[../../configItem/name = '%s' and not(configItem/countryList/iso3166Id)]",
459 XKBCR_VARIANT_PATH
460 "[../../configItem/countryList/iso3166Id = '%s' and not(configItem/countryList/iso3166Id)]",
461 NULL
462 };
463
464 const gboolean should_code_be_lowered2[] = { FALSE, TRUE, FALSE };
465
466 xkl_config_registry_foreach_iso_variant(config,
467 country_code,
468 func, data,
469 layout_xpath_exprs,
470 should_code_be_lowered1,
471 variant_xpath_exprs,
472 should_code_be_lowered2);
473 }
474
475 void
xkl_config_registry_foreach_language_variant(XklConfigRegistry * config,const gchar * language_code,XklTwoConfigItemsProcessFunc func,gpointer data)476 xkl_config_registry_foreach_language_variant(XklConfigRegistry *
477 config,
478 const gchar *
479 language_code,
480 XklTwoConfigItemsProcessFunc
481 func, gpointer data)
482 {
483 const gchar *layout_xpath_exprs[] = {
484 XKBCR_LAYOUT_PATH
485 "[configItem/languageList/iso639Id = '%s']",
486 NULL
487 };
488 const gboolean should_code_be_lowered1[] = { FALSE };
489
490 const gchar *variant_xpath_exprs[] = {
491 XKBCR_VARIANT_PATH
492 "[configItem/languageList/iso639Id = '%s']",
493 XKBCR_VARIANT_PATH
494 "[../../configItem/languageList/iso639Id = '%s' and not(configItem/languageList/iso639Id)]",
495 NULL
496 };
497 const gboolean should_code_be_lowered2[] = { FALSE, FALSE };
498
499 xkl_config_registry_foreach_iso_variant(config,
500 language_code,
501 func, data,
502 layout_xpath_exprs,
503 should_code_be_lowered1,
504 variant_xpath_exprs,
505 should_code_be_lowered2);
506 }
507