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