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 #include "utf8.c"
33 
34 static GObjectClass *parent_class = NULL;
35 
36 static xmlXPathCompExprPtr models_xpath;
37 static xmlXPathCompExprPtr layouts_xpath;
38 static xmlXPathCompExprPtr option_groups_xpath;
39 
40 static GRegex **xml_encode_regexen = NULL;
41 static GRegex **xml_decode_regexen = NULL;
42 static const char *xml_decode_regexen_str[] = { "&lt;", "&gt;", "&amp;" };
43 static const char *xml_encode_regexen_str[] = { "<", ">", "&" };
44 
45 /* gettext domain for translations */
46 #define XKB_DOMAIN "xkeyboard-config"
47 
48 enum {
49 	PROP_0,
50 	PROP_ENGINE
51 };
52 
53 typedef struct {
54 	gchar **patterns;
55 	XklTwoConfigItemsProcessFunc func;
56 	gpointer data;
57 	gboolean country_matched;
58 	gboolean language_matched;
59 	const XklConfigItem *layout_item;
60 } SearchParamType;
61 
62 static gboolean
xkl_xml_find_config_item_child(xmlNodePtr iptr,xmlNodePtr * ptr)63 xkl_xml_find_config_item_child(xmlNodePtr iptr, xmlNodePtr * ptr)
64 {
65 	/*
66 	 * Walking through the 1st level children of iptr
67 	 * looking for the configItem
68 	 */
69 	if (iptr->type != XML_ELEMENT_NODE)
70 		return FALSE;
71 	*ptr = iptr->children;
72 	while (*ptr != NULL) {
73 		switch ((*ptr)->type) {
74 		case XML_ELEMENT_NODE:
75 			return !g_ascii_strcasecmp
76 			    ((char *) (*ptr)->name, "configItem");
77 		case XML_TEXT_NODE:
78 		case XML_COMMENT_NODE:
79 			(*ptr) = (*ptr)->next;
80 			continue;
81 		default:
82 			return FALSE;
83 		}
84 		break;
85 	}
86 	return FALSE;
87 }
88 
89 static xmlNodePtr
xkl_find_element(xmlNodePtr ptr,const gchar * tag_name)90 xkl_find_element(xmlNodePtr ptr, const gchar * tag_name)
91 {
92 	xmlNodePtr found_element = NULL;
93 
94 	/* Look through all siblings, trying to find a node with proper name */
95 	while (ptr != NULL) {
96 		char *node_name = (char *) ptr->name;
97 		if (ptr->type != XML_TEXT_NODE) {
98 			if (!g_ascii_strcasecmp(node_name, tag_name)) {
99 				found_element = ptr;
100 				break;
101 			}
102 		}
103 		ptr = ptr->next;
104 	}
105 	return found_element;
106 }
107 
108 static gboolean
xkl_item_populate_optional_array(XklConfigItem * item,xmlNodePtr ptr,const gchar list_tag[],const gchar element_tag[],const gchar property_name[])109 xkl_item_populate_optional_array(XklConfigItem * item, xmlNodePtr ptr,
110 				 const gchar list_tag[],
111 				 const gchar element_tag[],
112 				 const gchar property_name[])
113 {
114 	xmlNodePtr top_list_element =
115 	    xkl_find_element(ptr, list_tag), element_ptr;
116 	gint n_elements, idx;
117 	gchar **elements = NULL;
118 
119 	if (top_list_element == NULL || top_list_element->children == NULL)
120 		return FALSE;
121 
122 	n_elements = 0;
123 
124 	/* First, count countries */
125 	element_ptr = top_list_element->children;
126 	while (NULL !=
127 	       (element_ptr =
128 		xkl_find_element(element_ptr, element_tag))) {
129 		n_elements++;
130 		element_ptr = element_ptr->next;
131 	}
132 
133 	if (n_elements == 0)
134 		return FALSE;
135 
136 	elements = g_new0(gchar *, n_elements + 1);
137 	/* Then, actually, populate the list */
138 	element_ptr = top_list_element->children;
139 	for (idx = 0;
140 	     NULL != (element_ptr =
141 		      xkl_find_element(element_ptr, element_tag));
142 	     element_ptr = element_ptr->next, idx++) {
143 		elements[idx] =
144 		    g_strdup((const char *) element_ptr->
145 			     children->content);
146 	}
147 
148 	g_object_set_data_full(G_OBJECT(item),
149 			       property_name, elements, (GDestroyNotify)
150 			       g_strfreev);
151 	return TRUE;
152 }
153 
154 #include "libxml/parserInternals.h"
155 
156 gboolean
xkl_read_config_item(XklConfigRegistry * config,gint doc_index,xmlNodePtr iptr,XklConfigItem * item)157 xkl_read_config_item(XklConfigRegistry * config, gint doc_index,
158 		     xmlNodePtr iptr, XklConfigItem * item)
159 {
160 	xmlNodePtr name_element, ptr;
161 	xmlNodePtr desc_element = NULL, short_desc_element =
162 	    NULL, vendor_element = NULL;
163 
164 	gchar *vendor = NULL, *translated = NULL, *escaped =
165 	    NULL, *unescaped = NULL;
166 
167 	guchar *s = NULL;
168 
169 	gint i;
170 
171 	*item->name = 0;
172 	*item->short_description = 0;
173 	*item->description = 0;
174 
175 	g_object_set_data(G_OBJECT(item), XCI_PROP_VENDOR, NULL);
176 	g_object_set_data(G_OBJECT(item), XCI_PROP_COUNTRY_LIST, NULL);
177 	g_object_set_data(G_OBJECT(item), XCI_PROP_LANGUAGE_LIST, NULL);
178 
179 	if (!xkl_xml_find_config_item_child(iptr, &ptr))
180 		return FALSE;
181 
182 	if (doc_index > 0)
183 		g_object_set_data(G_OBJECT(item), XCI_PROP_EXTRA_ITEM,
184 				  GINT_TO_POINTER(TRUE));
185 
186 	ptr = ptr->children;
187 
188 	if (ptr->type == XML_TEXT_NODE)
189 		ptr = ptr->next;
190 	name_element = ptr;
191 	ptr = ptr->next;
192 
193 	short_desc_element = xkl_find_element(ptr, XML_TAG_SHORT_DESCR);
194 	desc_element = xkl_find_element(ptr, XML_TAG_DESCR);
195 	vendor_element = xkl_find_element(ptr, XML_TAG_VENDOR);
196 
197 	if (name_element != NULL && name_element->children != NULL) {
198 		strncat(item->name,
199 			(char *) name_element->children->content,
200 			XKL_MAX_CI_NAME_LENGTH - 1);
201 
202 		s = utf8_check((guchar *)item->name);
203 
204 		for (i = strlen(item->name); i > 0 && s != NULL; i--) {
205 			item->name[i] = 0;
206 			s = utf8_check((guchar *)item->name);
207 		}
208 	}
209 
210 	if (short_desc_element != NULL
211 	    && short_desc_element->children != NULL) {
212 		strncat(item->short_description,
213 			dgettext(XKB_DOMAIN, (const char *)
214 				 short_desc_element->children->content),
215 			XKL_MAX_CI_SHORT_DESC_LENGTH - 1);
216 
217 		s = utf8_check((guchar *)item->short_description);
218 
219 		for (i = strlen(item->short_description); i > 0 && s != NULL; i--) {
220 			item->short_description[i] = 0;
221 			s = utf8_check((guchar *)item->short_description);
222 		}
223 	}
224 
225 	if (desc_element != NULL && desc_element->children != NULL) {
226 		/* Convert all xml-related characters to XML form, otherwise dgettext won't find the translation
227 		 * The conversion is not using libxml2, because there are no handy functions in API */
228 		translated =
229 		    g_strdup((gchar *) desc_element->children->content);
230 		for (i =
231 		     sizeof(xml_encode_regexen_str) /
232 		     sizeof(xml_encode_regexen_str[0]); --i >= 0;) {
233 			escaped =
234 			    g_regex_replace(xml_encode_regexen[i],
235 					    translated, -1, 0,
236 					    xml_decode_regexen_str[i], 0,
237 					    NULL);
238 			g_free(translated);
239 			translated = escaped;
240 		}
241 		escaped = translated;
242 
243 		/* Do the translation! */
244 		translated =
245 		    g_strdup(dgettext(XKB_DOMAIN, (const char *) escaped));
246 		g_free(escaped);
247 
248 		/* Convert all XML entities back to normal form */
249 		for (i =
250 		     sizeof(xml_decode_regexen_str) /
251 		     sizeof(xml_decode_regexen_str[0]); --i >= 0;) {
252 			unescaped =
253 			    g_regex_replace(xml_decode_regexen[i],
254 					    translated, -1, 0,
255 					    xml_encode_regexen_str[i], 0,
256 					    NULL);
257 			g_free(translated);
258 			translated = unescaped;
259 		}
260 
261 		strncat(item->description,
262 			translated, XKL_MAX_CI_DESC_LENGTH - 1);
263 
264 		s = utf8_check((guchar *)item->description);
265 
266 		for (i = strlen(item->description); i > 0 && s != NULL; i--) {
267 			item->description[i] = 0;
268 			s = utf8_check((guchar *)item->description);
269 		}
270 
271 		g_free(s);
272 		g_free(translated);
273 	}
274 
275 	if (vendor_element != NULL && vendor_element->children != NULL) {
276 		vendor =
277 		    g_strdup((const char *) vendor_element->children->
278 			     content);
279 		g_object_set_data_full(G_OBJECT(item), XCI_PROP_VENDOR,
280 				       vendor, g_free);
281 	}
282 
283 	xkl_item_populate_optional_array(item, ptr, XML_TAG_COUNTRY_LIST,
284 					 XML_TAG_ISO3166ID,
285 					 XCI_PROP_COUNTRY_LIST);
286 	xkl_item_populate_optional_array(item, ptr, XML_TAG_LANGUAGE_LIST,
287 					 XML_TAG_ISO639ID,
288 					 XCI_PROP_LANGUAGE_LIST);
289 
290 	return TRUE;
291 }
292 
293 static void
xkl_config_registry_foreach_in_nodeset(XklConfigRegistry * config,GSList ** processed_ids,gint doc_index,xmlNodeSetPtr nodes,XklConfigItemProcessFunc func,gpointer data)294 xkl_config_registry_foreach_in_nodeset(XklConfigRegistry * config,
295 				       GSList ** processed_ids,
296 				       gint doc_index, xmlNodeSetPtr nodes,
297 				       XklConfigItemProcessFunc func,
298 				       gpointer data)
299 {
300 	gint i;
301 	if (nodes != NULL) {
302 		xmlNodePtr *pnode = nodes->nodeTab;
303 		XklConfigItem *ci = xkl_config_item_new();
304 		for (i = nodes->nodeNr; --i >= 0;) {
305 			if (xkl_read_config_item
306 			    (config, doc_index, *pnode, ci)) {
307 				if (g_slist_find_custom
308 				    (*processed_ids, ci->name,
309 				     (GCompareFunc) g_ascii_strcasecmp) ==
310 				    NULL) {
311 					func(config, ci, data);
312 					*processed_ids =
313 					    g_slist_append(*processed_ids,
314 							   g_strdup
315 							   (ci->name));
316 				}
317 			}
318 
319 			pnode++;
320 		}
321 		g_object_unref(G_OBJECT(ci));
322 	}
323 }
324 
325 void
xkl_config_registry_foreach_in_xpath(XklConfigRegistry * config,xmlXPathCompExprPtr xpath_comp_expr,XklConfigItemProcessFunc func,gpointer data)326 xkl_config_registry_foreach_in_xpath(XklConfigRegistry * config,
327 				     xmlXPathCompExprPtr
328 				     xpath_comp_expr,
329 				     XklConfigItemProcessFunc func,
330 				     gpointer data)
331 {
332 	xmlXPathObjectPtr xpath_obj;
333 	gint di;
334 	GSList *processed_ids = NULL;
335 
336 	if (!xkl_config_registry_is_initialized(config))
337 		return;
338 
339 	for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
340 		xmlXPathContextPtr xmlctxt =
341 		    xkl_config_registry_priv(config, xpath_contexts[di]);
342 		if (xmlctxt == NULL)
343 			continue;
344 
345 		xpath_obj = xmlXPathCompiledEval(xpath_comp_expr, xmlctxt);
346 		if (xpath_obj == NULL)
347 			continue;
348 
349 		xkl_config_registry_foreach_in_nodeset(config,
350 						       &processed_ids, di,
351 						       xpath_obj->
352 						       nodesetval, func,
353 						       data);
354 		xmlXPathFreeObject(xpath_obj);
355 	}
356 	g_slist_foreach(processed_ids, (GFunc) g_free, NULL);
357 	g_slist_free(processed_ids);
358 }
359 
360 void
xkl_config_registry_foreach_in_xpath_with_param(XklConfigRegistry * config,const gchar * format,const gchar * value,XklConfigItemProcessFunc func,gpointer data)361 xkl_config_registry_foreach_in_xpath_with_param(XklConfigRegistry
362 						* config,
363 						const gchar *
364 						format,
365 						const gchar *
366 						value,
367 						XklConfigItemProcessFunc
368 						func, gpointer data)
369 {
370 	char xpath_expr[1024];
371 	xmlXPathObjectPtr xpath_obj;
372 	gint di;
373 	GSList *processed_ids = NULL;
374 
375 	if (!xkl_config_registry_is_initialized(config))
376 		return;
377 
378 	g_snprintf(xpath_expr, sizeof xpath_expr, format, value);
379 
380 	for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
381 		xmlXPathContextPtr xmlctxt =
382 		    xkl_config_registry_priv(config, xpath_contexts[di]);
383 		if (xmlctxt == NULL)
384 			continue;
385 
386 		xpath_obj =
387 		    xmlXPathEval((unsigned char *) xpath_expr, xmlctxt);
388 		if (xpath_obj == NULL)
389 			continue;
390 
391 		xkl_config_registry_foreach_in_nodeset(config,
392 						       &processed_ids, di,
393 						       xpath_obj->
394 						       nodesetval, func,
395 						       data);
396 		xmlXPathFreeObject(xpath_obj);
397 	}
398 	g_slist_foreach(processed_ids, (GFunc) g_free, NULL);
399 	g_slist_free(processed_ids);
400 }
401 
402 static gboolean
xkl_config_registry_find_object(XklConfigRegistry * config,const gchar * format,const gchar * arg1,XklConfigItem * pitem,xmlNodePtr * pnode)403 xkl_config_registry_find_object(XklConfigRegistry * config,
404 				const gchar * format,
405 				const gchar * arg1,
406 				XklConfigItem * pitem /* in/out */ ,
407 				xmlNodePtr * pnode /* out */ )
408 {
409 	xmlXPathObjectPtr xpath_obj;
410 	xmlNodeSetPtr nodes;
411 	gboolean rv = FALSE;
412 	gchar xpath_expr[1024];
413 	gint di;
414 
415 	if (!xkl_config_registry_is_initialized(config))
416 		return FALSE;
417 
418 	g_snprintf(xpath_expr, sizeof xpath_expr, format, arg1,
419 		   pitem->name);
420 
421 	for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
422 		xmlXPathContextPtr xmlctxt =
423 		    xkl_config_registry_priv(config, xpath_contexts[di]);
424 		if (xmlctxt == NULL)
425 			continue;
426 
427 		xpath_obj =
428 		    xmlXPathEval((unsigned char *) xpath_expr, xmlctxt);
429 		if (xpath_obj == NULL)
430 			continue;
431 
432 		nodes = xpath_obj->nodesetval;
433 		if (nodes != NULL && nodes->nodeTab != NULL
434 		    && nodes->nodeNr > 0) {
435 			rv = xkl_read_config_item(config, di,
436 						  nodes->nodeTab[0],
437 						  pitem);
438 			if (pnode != NULL) {
439 				*pnode = *nodes->nodeTab;
440 			}
441 		}
442 
443 		xmlXPathFreeObject(xpath_obj);
444 	}
445 	return rv;
446 }
447 
448 gchar *
xkl_config_rec_merge_layouts(const XklConfigRec * data)449 xkl_config_rec_merge_layouts(const XklConfigRec * data)
450 {
451 	return xkl_strings_concat_comma_separated(data->layouts);
452 }
453 
454 gchar *
xkl_config_rec_merge_variants(const XklConfigRec * data)455 xkl_config_rec_merge_variants(const XklConfigRec * data)
456 {
457 	return xkl_strings_concat_comma_separated(data->variants);
458 }
459 
460 gchar *
xkl_config_rec_merge_options(const XklConfigRec * data)461 xkl_config_rec_merge_options(const XklConfigRec * data)
462 {
463 	return xkl_strings_concat_comma_separated(data->options);
464 }
465 
466 gchar *
xkl_strings_concat_comma_separated(gchar ** array)467 xkl_strings_concat_comma_separated(gchar ** array)
468 {
469 	if (array) {
470 		return g_strjoinv(",", array);
471 	} else {
472 		return g_strdup("");
473 	}
474 }
475 
476 void
xkl_config_rec_split_layouts(XklConfigRec * data,const gchar * merged)477 xkl_config_rec_split_layouts(XklConfigRec * data, const gchar * merged)
478 {
479 	xkl_strings_split_comma_separated(&data->layouts, merged);
480 }
481 
482 void
xkl_config_rec_split_variants(XklConfigRec * data,const gchar * merged)483 xkl_config_rec_split_variants(XklConfigRec * data, const gchar * merged)
484 {
485 	xkl_strings_split_comma_separated(&data->variants, merged);
486 }
487 
488 void
xkl_config_rec_split_options(XklConfigRec * data,const gchar * merged)489 xkl_config_rec_split_options(XklConfigRec * data, const gchar * merged)
490 {
491 	xkl_strings_split_comma_separated(&data->options, merged);
492 }
493 
494 void
xkl_strings_split_comma_separated(gchar *** array,const gchar * merged)495 xkl_strings_split_comma_separated(gchar *** array, const gchar * merged)
496 {
497 	*array = g_strsplit(merged, ",", 0);
498 }
499 
500 gchar *
xkl_engine_get_ruleset_name(XklEngine * engine,const gchar default_ruleset[])501 xkl_engine_get_ruleset_name(XklEngine * engine,
502 			    const gchar default_ruleset[])
503 {
504 	static gchar rules_set_name[1024] = "";
505 	if (!rules_set_name[0]) {
506 		/* first call */
507 		gchar *rf = NULL;
508 		if (!xkl_config_rec_get_from_root_window_property
509 		    (NULL,
510 		     xkl_engine_priv(engine, base_config_atom),
511 		     &rf, engine) || (rf == NULL)) {
512 			g_strlcpy(rules_set_name, default_ruleset,
513 				  sizeof rules_set_name);
514 			xkl_debug(100,
515 				  "Using default rules set: [%s]\n",
516 				  rules_set_name);
517 			return rules_set_name;
518 		}
519 		g_strlcpy(rules_set_name, rf, sizeof rules_set_name);
520 		g_free(rf);
521 	}
522 	xkl_debug(100, "Rules set: [%s]\n", rules_set_name);
523 	return rules_set_name;
524 }
525 
526 XklConfigRegistry *
xkl_config_registry_get_instance(XklEngine * engine)527 xkl_config_registry_get_instance(XklEngine * engine)
528 {
529 	XklConfigRegistry *config;
530 
531 	if (!engine) {
532 		xkl_debug(10,
533 			  "xkl_config_registry_get_instance : engine is NULL ?\n");
534 		return NULL;
535 	}
536 
537 	config =
538 	    XKL_CONFIG_REGISTRY(g_object_new
539 				(xkl_config_registry_get_type(),
540 				 "engine", engine, NULL));
541 
542 	return config;
543 }
544 
545 /* We process descriptions as "leaf" elements - this is ok for base.xml*/
546 gboolean
xkl_config_registry_load_from_file(XklConfigRegistry * config,const gchar * file_name,gint docidx)547 xkl_config_registry_load_from_file(XklConfigRegistry * config,
548 				   const gchar * file_name, gint docidx)
549 {
550 	xmlParserCtxtPtr ctxt = xmlNewParserCtxt();
551 	xmlDocPtr doc;
552 
553 	xkl_debug(100, "Loading XML registry from file %s\n", file_name);
554 
555 	xmlSAX2InitDefaultSAXHandler(ctxt->sax, TRUE);
556 
557 	doc = xkl_config_registry_priv(config, docs[docidx]) =
558 	    xmlCtxtReadFile(ctxt, file_name, NULL, XML_PARSE_NOBLANKS);
559 	xmlFreeParserCtxt(ctxt);
560 
561 	if (doc == NULL) {
562 		xkl_config_registry_priv(config, xpath_contexts[docidx]) =
563 		    NULL;
564 		xkl_last_error_message =
565 		    "Could not parse primary XKB configuration registry";
566 		return FALSE;
567 	}
568 
569 	xkl_config_registry_priv(config, xpath_contexts[docidx]) =
570 	    xmlXPathNewContext(doc);
571 
572 	return TRUE;
573 }
574 
575 gboolean
xkl_config_registry_load_helper(XklConfigRegistry * config,const char default_ruleset[],const char base_dir[],gboolean if_extras_needed)576 xkl_config_registry_load_helper(XklConfigRegistry * config,
577 				const char default_ruleset[],
578 				const char base_dir[],
579 				gboolean if_extras_needed)
580 {
581 	struct stat stat_buf;
582 	gchar file_name[MAXPATHLEN] = "";
583 	XklEngine *engine = xkl_config_registry_get_engine(config);
584 	gchar *rf = xkl_engine_get_ruleset_name(engine, default_ruleset);
585 
586 	if (rf == NULL || rf[0] == '\0')
587 		return FALSE;
588 
589 	g_snprintf(file_name, sizeof file_name, "%s/%s.xml", base_dir, rf);
590 
591 	if (stat(file_name, &stat_buf) != 0) {
592 		xkl_debug(0, "Missing registry file %s\n", file_name);
593 		xkl_last_error_message = "Missing registry file";
594 		return FALSE;
595 	}
596 
597 	if (!xkl_config_registry_load_from_file(config, file_name, 0))
598 		return FALSE;
599 
600 	if (!if_extras_needed)
601 		return TRUE;
602 
603 	g_snprintf(file_name, sizeof file_name, "%s/%s.extras.xml",
604 		   base_dir, rf);
605 
606 	/* no extras - ok, no problem */
607 	if (stat(file_name, &stat_buf) != 0)
608 		return TRUE;
609 
610 	return xkl_config_registry_load_from_file(config, file_name, 1);
611 }
612 
613 void
xkl_config_registry_free(XklConfigRegistry * config)614 xkl_config_registry_free(XklConfigRegistry * config)
615 {
616 	if (xkl_config_registry_is_initialized(config)) {
617 		gint di;
618 		for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
619 			xmlXPathContextPtr xmlctxt =
620 			    xkl_config_registry_priv(config,
621 						     xpath_contexts[di]);
622 			if (xmlctxt == NULL)
623 				continue;
624 
625 			xmlXPathFreeContext(xmlctxt);
626 			xmlFreeDoc(xkl_config_registry_priv
627 				   (config, docs[di]));
628 			xkl_config_registry_priv(config,
629 						 xpath_contexts[di]) =
630 			    NULL;
631 			xkl_config_registry_priv(config, docs[di]) = NULL;
632 		}
633 
634 	}
635 }
636 
637 void
xkl_config_registry_foreach_model(XklConfigRegistry * config,XklConfigItemProcessFunc func,gpointer data)638 xkl_config_registry_foreach_model(XklConfigRegistry * config,
639 				  XklConfigItemProcessFunc func,
640 				  gpointer data)
641 {
642 	xkl_config_registry_foreach_in_xpath(config, models_xpath,
643 					     func, data);
644 }
645 
646 void
xkl_config_registry_foreach_layout(XklConfigRegistry * config,XklConfigItemProcessFunc func,gpointer data)647 xkl_config_registry_foreach_layout(XklConfigRegistry * config,
648 				   XklConfigItemProcessFunc func,
649 				   gpointer data)
650 {
651 	xkl_config_registry_foreach_in_xpath(config, layouts_xpath,
652 					     func, data);
653 }
654 
655 void
xkl_config_registry_foreach_layout_variant(XklConfigRegistry * config,const gchar * layout_name,XklConfigItemProcessFunc func,gpointer data)656 xkl_config_registry_foreach_layout_variant(XklConfigRegistry *
657 					   config,
658 					   const gchar *
659 					   layout_name,
660 					   XklConfigItemProcessFunc
661 					   func, gpointer data)
662 {
663 	xkl_config_registry_foreach_in_xpath_with_param(config,
664 							XKBCR_VARIANT_PATH
665 							"[../../configItem/name = '%s']",
666 							layout_name,
667 							func, data);
668 }
669 
670 void
xkl_config_registry_foreach_option_group(XklConfigRegistry * config,XklConfigItemProcessFunc func,gpointer data)671 xkl_config_registry_foreach_option_group(XklConfigRegistry *
672 					 config,
673 					 XklConfigItemProcessFunc
674 					 func, gpointer data)
675 {
676 	xmlXPathObjectPtr xpath_obj;
677 	gint di, j;
678 	GSList *processed_ids = NULL;
679 
680 	if (!xkl_config_registry_is_initialized(config))
681 		return;
682 
683 	for (di = 0; di < XKL_NUMBER_OF_REGISTRY_DOCS; di++) {
684 		xmlNodeSetPtr nodes;
685 		xmlNodePtr *pnode;
686 		XklConfigItem *ci;
687 
688 		xmlXPathContextPtr xmlctxt =
689 		    xkl_config_registry_priv(config, xpath_contexts[di]);
690 		if (xmlctxt == NULL)
691 			continue;
692 
693 		xpath_obj =
694 		    xmlXPathCompiledEval(option_groups_xpath, xmlctxt);
695 		if (xpath_obj == NULL)
696 			continue;
697 
698 		nodes = xpath_obj->nodesetval;
699 		pnode = nodes->nodeTab;
700 		ci = xkl_config_item_new();
701 		for (j = nodes->nodeNr; --j >= 0;) {
702 
703 			if (xkl_read_config_item(config, di, *pnode, ci)) {
704 				if (g_slist_find_custom
705 				    (processed_ids, ci->name,
706 				     (GCompareFunc) g_ascii_strcasecmp) ==
707 				    NULL) {
708 					gboolean allow_multisel = TRUE;
709 					xmlChar *sallow_multisel =
710 					    xmlGetProp(*pnode,
711 						       (unsigned char *)
712 						       XCI_PROP_ALLOW_MULTIPLE_SELECTION);
713 					if (sallow_multisel != NULL) {
714 						allow_multisel =
715 						    !g_ascii_strcasecmp
716 						    ("true", (char *)
717 						     sallow_multisel);
718 						xmlFree(sallow_multisel);
719 						g_object_set_data(G_OBJECT
720 								  (ci),
721 								  XCI_PROP_ALLOW_MULTIPLE_SELECTION,
722 								  GINT_TO_POINTER
723 								  (allow_multisel));
724 					}
725 
726 					func(config, ci, data);
727 					processed_ids =
728 					    g_slist_append(processed_ids,
729 							   g_strdup
730 							   (ci->name));
731 				}
732 			}
733 
734 			pnode++;
735 		}
736 		g_object_unref(G_OBJECT(ci));
737 		xmlXPathFreeObject(xpath_obj);
738 	}
739 	g_slist_foreach(processed_ids, (GFunc) g_free, NULL);
740 	g_slist_free(processed_ids);
741 }
742 
743 void
xkl_config_registry_foreach_option(XklConfigRegistry * config,const gchar * option_group_name,XklConfigItemProcessFunc func,gpointer data)744 xkl_config_registry_foreach_option(XklConfigRegistry * config,
745 				   const gchar *
746 				   option_group_name,
747 				   XklConfigItemProcessFunc func,
748 				   gpointer data)
749 {
750 	xkl_config_registry_foreach_in_xpath_with_param(config,
751 							XKBCR_OPTION_PATH
752 							"[../configItem/name = '%s']",
753 							option_group_name,
754 							func, data);
755 }
756 
757 static gboolean
search_all(const gchar * haystack,gchar ** needles)758 search_all(const gchar * haystack, gchar ** needles)
759 {
760 	/* match anything */
761 	if (!needles || !*needles)
762 		return TRUE;
763 
764 	gchar *uchs = g_utf8_strup(haystack, -1);
765 	do {
766 		if (g_strstr_len(uchs, -1, *needles) == NULL) {
767 			g_free(uchs);
768 			return FALSE;
769 		}
770 		needles++;
771 	} while (*needles);
772 
773 	g_free(uchs);
774 	return TRUE;
775 }
776 
777 static gboolean
if_country_matches_pattern(const XklConfigItem * item,gchar ** patterns,const gboolean check_name)778 if_country_matches_pattern(const XklConfigItem * item,
779 			   gchar ** patterns, const gboolean check_name)
780 {
781 	const gchar *country_desc;
782 	if (check_name) {
783 		gchar *upper_name = g_ascii_strup(item->name, -1);
784 		country_desc = xkl_get_country_name(upper_name);
785 		g_free(upper_name);
786 		xkl_debug(200, "Checking layout country: [%s]\n",
787 			  country_desc);
788 		if ((country_desc != NULL)
789 		    && search_all(country_desc, patterns))
790 			return TRUE;
791 	}
792 
793 	gchar **countries = g_object_get_data(G_OBJECT(item),
794 					      XCI_PROP_COUNTRY_LIST);
795 	for (; countries && *countries; countries++) {
796 		country_desc = xkl_get_country_name(*countries);
797 		xkl_debug(200, "Checking country: [%s][%s]\n",
798 			  *countries, country_desc);
799 		if ((country_desc != NULL)
800 		    && search_all(country_desc, patterns)) {
801 			return TRUE;
802 		}
803 	}
804 	return FALSE;
805 }
806 
807 static gboolean
if_language_matches_pattern(const XklConfigItem * item,gchar ** patterns,const gboolean check_name)808 if_language_matches_pattern(const XklConfigItem * item,
809 			    gchar ** patterns, const gboolean check_name)
810 {
811 	const gchar *language_desc;
812 	if (check_name) {
813 		language_desc = xkl_get_language_name(item->name);
814 		xkl_debug(200, "Checking layout language: [%s]\n",
815 			  language_desc);
816 		if ((language_desc != NULL)
817 		    && search_all(language_desc, patterns))
818 			return TRUE;
819 	}
820 	gchar **languages = g_object_get_data(G_OBJECT(item),
821 					      XCI_PROP_LANGUAGE_LIST);
822 	for (; languages && *languages; languages++) {
823 		language_desc = xkl_get_language_name(*languages);
824 		xkl_debug(200, "Checking language: [%s][%s]\n",
825 			  *languages, language_desc);
826 		if ((language_desc != NULL)
827 		    && search_all(language_desc, patterns)) {
828 			return TRUE;
829 		}
830 	}
831 	return FALSE;
832 }
833 
834 static void
xkl_config_registry_search_by_pattern_in_variant(XklConfigRegistry * config,const XklConfigItem * item,SearchParamType * search_param)835 xkl_config_registry_search_by_pattern_in_variant(XklConfigRegistry *
836 						 config,
837 						 const XklConfigItem *
838 						 item,
839 						 SearchParamType *
840 						 search_param)
841 {
842 	gboolean variant_matched = FALSE;
843 	gchar *full_desc = g_strdup_printf("%s - %s",
844 					   search_param->
845 					   layout_item->description,
846 					   item->description);
847 
848 	xkl_debug(200, "Variant to check: [%s][%s]\n", item->name,
849 		  item->description);
850 
851 	if (search_all(full_desc, search_param->patterns))
852 		variant_matched = TRUE;
853 
854 	g_free(full_desc);
855 
856 	if (!variant_matched) {
857 		gchar **countries = g_object_get_data(G_OBJECT(item),
858 						      XCI_PROP_COUNTRY_LIST);
859 		if (countries && g_strv_length(countries) > 0) {
860 			if (if_country_matches_pattern
861 			    (item, search_param->patterns, FALSE))
862 				variant_matched = TRUE;
863 		} else {
864 			if (search_param->country_matched)
865 				variant_matched = TRUE;
866 		}
867 	}
868 
869 	if (!variant_matched) {
870 		gchar **languages = g_object_get_data(G_OBJECT(item),
871 						      XCI_PROP_LANGUAGE_LIST);
872 		if (languages && g_strv_length(languages) > 0) {
873 			if (if_language_matches_pattern
874 			    (item, search_param->patterns, FALSE))
875 				variant_matched = TRUE;
876 		} else {
877 			if (search_param->language_matched)
878 				variant_matched = TRUE;
879 		}
880 	}
881 
882 	if (variant_matched)
883 		(search_param->func) (config, search_param->layout_item,
884 				      item, search_param->data);
885 }
886 
887 static void
xkl_config_registry_search_by_pattern_in_layout(XklConfigRegistry * config,const XklConfigItem * item,SearchParamType * search_param)888 xkl_config_registry_search_by_pattern_in_layout(XklConfigRegistry * config,
889 						const XklConfigItem * item,
890 						SearchParamType *
891 						search_param)
892 {
893 	gchar *upper_name = g_ascii_strup(item->name, -1);
894 
895 	xkl_debug(200, "Layout to check: [%s][%s]\n", item->name,
896 		  item->description);
897 
898 	search_param->country_matched =
899 	    search_param->language_matched = FALSE;
900 
901 	if (if_country_matches_pattern(item, search_param->patterns, TRUE))
902 		search_param->country_matched = TRUE;
903 	else if (if_language_matches_pattern
904 		 (item, search_param->patterns, TRUE))
905 		search_param->language_matched = TRUE;
906 	else if (search_all(item->description, search_param->patterns))
907 		search_param->language_matched = TRUE;
908 
909 	if (search_param->country_matched
910 	    || search_param->language_matched)
911 		(search_param->func) (config, item, NULL,
912 				      search_param->data);
913 
914 	search_param->layout_item = item;
915 
916 	xkl_config_registry_foreach_layout_variant(config, item->name,
917 						   (XklConfigItemProcessFunc)
918 						   xkl_config_registry_search_by_pattern_in_variant,
919 						   search_param);
920 
921 	g_free(upper_name);
922 }
923 
924 void
xkl_config_registry_search_by_pattern(XklConfigRegistry * config,const gchar * pattern,XklTwoConfigItemsProcessFunc func,gpointer data)925 xkl_config_registry_search_by_pattern(XklConfigRegistry
926 				      * config,
927 				      const gchar *
928 				      pattern,
929 				      XklTwoConfigItemsProcessFunc
930 				      func, gpointer data)
931 {
932 	xkl_debug(200, "Searching by pattern: [%s]\n", pattern);
933 	gchar *upattern = pattern ? g_utf8_strup(pattern, -1) : NULL;
934 	gchar **patterns = pattern ? g_strsplit(upattern, " ", -1) : NULL;
935 	SearchParamType search_param = {
936 		patterns, func, data
937 	};
938 	xkl_config_registry_foreach_layout(config, (XklConfigItemProcessFunc)
939 					   xkl_config_registry_search_by_pattern_in_layout,
940 					   &search_param);
941 	g_strfreev(patterns);
942 	g_free(upattern);
943 }
944 
945 gboolean
xkl_config_registry_find_model(XklConfigRegistry * config,XklConfigItem * pitem)946 xkl_config_registry_find_model(XklConfigRegistry *
947 			       config, XklConfigItem * pitem /* in/out */ )
948 {
949 	return xkl_config_registry_find_object(config,
950 					       XKBCR_MODEL_PATH
951 					       "[configItem/name = '%s%s']",
952 					       "", pitem, NULL);
953 }
954 
955 gboolean
xkl_config_registry_find_layout(XklConfigRegistry * config,XklConfigItem * pitem)956 xkl_config_registry_find_layout(XklConfigRegistry *
957 				config,
958 				XklConfigItem * pitem /* in/out */ )
959 {
960 	return xkl_config_registry_find_object(config,
961 					       XKBCR_LAYOUT_PATH
962 					       "[configItem/name = '%s%s']",
963 					       "", pitem, NULL);
964 }
965 
966 gboolean
xkl_config_registry_find_variant(XklConfigRegistry * config,const char * layout_name,XklConfigItem * pitem)967 xkl_config_registry_find_variant(XklConfigRegistry *
968 				 config,
969 				 const char
970 				 *layout_name,
971 				 XklConfigItem * pitem /* in/out */ )
972 {
973 	return xkl_config_registry_find_object(config,
974 					       XKBCR_VARIANT_PATH
975 					       "[../../configItem/name = '%s' and configItem/name = '%s']",
976 					       layout_name, pitem, NULL);
977 }
978 
979 gboolean
xkl_config_registry_find_option_group(XklConfigRegistry * config,XklConfigItem * pitem)980 xkl_config_registry_find_option_group(XklConfigRegistry * config, XklConfigItem * pitem	/* in/out */
981     )
982 {
983 	xmlNodePtr node = NULL;
984 	gboolean rv = xkl_config_registry_find_object(config,
985 						      XKBCR_GROUP_PATH
986 						      "[configItem/name = '%s%s']",
987 						      "", pitem, &node);
988 	if (rv) {
989 		xmlChar *val = xmlGetProp(node, (unsigned char *)
990 					  XCI_PROP_ALLOW_MULTIPLE_SELECTION);
991 		if (val != NULL) {
992 			gboolean allow_multisel =
993 			    !g_ascii_strcasecmp("true",
994 						(char *) val);
995 			g_object_set_data(G_OBJECT(pitem),
996 					  XCI_PROP_ALLOW_MULTIPLE_SELECTION,
997 					  GINT_TO_POINTER(allow_multisel));
998 			xmlFree(val);
999 		}
1000 	}
1001 	return rv;
1002 }
1003 
1004 gboolean
xkl_config_registry_find_option(XklConfigRegistry * config,const char * option_group_name,XklConfigItem * pitem)1005 xkl_config_registry_find_option(XklConfigRegistry *
1006 				config,
1007 				const char
1008 				*option_group_name,
1009 				XklConfigItem * pitem /* in/out */ )
1010 {
1011 	return xkl_config_registry_find_object(config,
1012 					       XKBCR_OPTION_PATH
1013 					       "[../configItem/name = '%s' and configItem/name = '%s']",
1014 					       option_group_name,
1015 					       pitem, NULL);
1016 }
1017 
1018 /*
1019  * Calling through vtable
1020  */
1021 gboolean
xkl_config_rec_activate(const XklConfigRec * data,XklEngine * engine)1022 xkl_config_rec_activate(const XklConfigRec * data, XklEngine * engine)
1023 {
1024 	xkl_engine_ensure_vtable_inited(engine);
1025 	return xkl_engine_vcall(engine,
1026 				activate_config_rec) (engine, data);
1027 }
1028 
1029 gboolean
xkl_config_registry_load(XklConfigRegistry * config,gboolean if_extras_needed)1030 xkl_config_registry_load(XklConfigRegistry * config,
1031 			 gboolean if_extras_needed)
1032 {
1033 	XklEngine *engine;
1034 	xkl_config_registry_free(config);
1035 	engine = xkl_config_registry_get_engine(config);
1036 	xkl_engine_ensure_vtable_inited(engine);
1037 	return xkl_engine_vcall(engine,
1038 				load_config_registry) (config,
1039 						       if_extras_needed);
1040 }
1041 
1042 gboolean
xkl_config_rec_write_to_file(XklEngine * engine,const gchar * file_name,const XklConfigRec * data,const gboolean binary)1043 xkl_config_rec_write_to_file(XklEngine * engine,
1044 			     const gchar * file_name,
1045 			     const XklConfigRec * data,
1046 			     const gboolean binary)
1047 {
1048 	if ((!binary
1049 	     && !(xkl_engine_priv(engine, features) &
1050 		  XKLF_CAN_OUTPUT_CONFIG_AS_ASCII))
1051 	    || (binary
1052 		&& !(xkl_engine_priv(engine, features) &
1053 		     XKLF_CAN_OUTPUT_CONFIG_AS_BINARY))) {
1054 		xkl_last_error_message =
1055 		    "Function not supported at backend";
1056 		return FALSE;
1057 	}
1058 	xkl_engine_ensure_vtable_inited(engine);
1059 	return xkl_engine_vcall(engine, write_config_rec_to_file)
1060 	    (engine, file_name, data, binary);
1061 }
1062 
1063 void
xkl_config_rec_dump(FILE * file,XklConfigRec * data)1064 xkl_config_rec_dump(FILE * file, XklConfigRec * data)
1065 {
1066 	int j;
1067 	fprintf(file, "  model: [%s]\n", data->model);
1068 	fprintf(file, "  layouts:\n");
1069 #define OUTPUT_ARRZ(arrz) \
1070   { \
1071     gchar **p = data->arrz; \
1072     fprintf( file, "  " #arrz ":\n" ); \
1073     if ( p != NULL ) \
1074       for( j = 0; *p != NULL; ) \
1075         fprintf( file, "  %d: [%s]\n", j++, *p++ ); \
1076   }
1077 	OUTPUT_ARRZ(layouts);
1078 	OUTPUT_ARRZ(variants);
1079 	OUTPUT_ARRZ(options);
1080 }
1081 
G_DEFINE_TYPE(XklConfigRegistry,xkl_config_registry,G_TYPE_OBJECT)1082 G_DEFINE_TYPE(XklConfigRegistry, xkl_config_registry, G_TYPE_OBJECT)
1083 static GObject *
1084 xkl_config_registry_constructor(GType type,
1085 				guint
1086 				n_construct_properties,
1087 				GObjectConstructParam *
1088 				construct_properties)
1089 {
1090 	GObject *obj;
1091 	XklConfigRegistry *config;
1092 	XklEngine *engine; {
1093 		/* Invoke parent constructor. */
1094 		g_type_class_peek(XKL_TYPE_CONFIG_REGISTRY);
1095 		obj =
1096 		    parent_class->constructor(type,
1097 					      n_construct_properties,
1098 					      construct_properties);
1099 	}
1100 
1101 	config = XKL_CONFIG_REGISTRY(obj);
1102 	engine = XKL_ENGINE(g_value_peek_pointer
1103 			    (construct_properties[0].value));
1104 	xkl_config_registry_get_engine(config) = engine;
1105 	xkl_engine_ensure_vtable_inited(engine);
1106 	xkl_engine_vcall(engine, init_config_registry) (config);
1107 	return obj;
1108 }
1109 
1110 static void
xkl_config_registry_init(XklConfigRegistry * config)1111 xkl_config_registry_init(XklConfigRegistry * config)
1112 {
1113 	config->priv = g_new0(XklConfigRegistryPrivate, 1);
1114 }
1115 
1116 static void
xkl_config_registry_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)1117 xkl_config_registry_set_property(GObject * object,
1118 				 guint property_id,
1119 				 const GValue * value, GParamSpec * pspec)
1120 {
1121 }
1122 
1123 static void
xkl_config_registry_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)1124 xkl_config_registry_get_property(GObject * object,
1125 				 guint property_id,
1126 				 GValue * value, GParamSpec * pspec)
1127 {
1128 	XklConfigRegistry *config = XKL_CONFIG_REGISTRY(object);
1129 	switch (property_id) {
1130 	case PROP_ENGINE:
1131 		g_value_set_pointer(value,
1132 				    xkl_config_registry_get_engine
1133 				    (config));
1134 		break;
1135 	}
1136 
1137 }
1138 
1139 static void
xkl_config_registry_finalize(GObject * obj)1140 xkl_config_registry_finalize(GObject * obj)
1141 {
1142 	XklConfigRegistry *config = (XklConfigRegistry *) obj;
1143 	xkl_config_registry_free(config);
1144 	g_free(config->priv);
1145 	G_OBJECT_CLASS(parent_class)->finalize(obj);
1146 }
1147 
1148 /*
1149  * This function is actually NEVER called.
1150  * It is 'extern' just to avoid the compilation warnings
1151  * TODO: add class cleanup
1152  */
1153 extern void
xkl_config_registry_class_term(XklConfigRegistryClass * klass)1154 xkl_config_registry_class_term(XklConfigRegistryClass * klass)
1155 {
1156 	gint i;
1157 	if (models_xpath != NULL) {
1158 		xmlXPathFreeCompExpr(models_xpath);
1159 		models_xpath = NULL;
1160 	}
1161 	if (layouts_xpath != NULL) {
1162 		xmlXPathFreeCompExpr(layouts_xpath);
1163 		layouts_xpath = NULL;
1164 	}
1165 	if (option_groups_xpath != NULL) {
1166 		xmlXPathFreeCompExpr(option_groups_xpath);
1167 		option_groups_xpath = NULL;
1168 	}
1169 	if (xml_encode_regexen != NULL) {
1170 		for (i =
1171 		     sizeof(xml_encode_regexen_str) /
1172 		     sizeof(xml_encode_regexen_str[0]); --i >= 0;) {
1173 			g_regex_unref(xml_encode_regexen[i]);
1174 		}
1175 		g_free(xml_encode_regexen);
1176 		xml_encode_regexen = NULL;
1177 	}
1178 	if (xml_decode_regexen != NULL) {
1179 		for (i =
1180 		     sizeof(xml_decode_regexen_str) /
1181 		     sizeof(xml_decode_regexen_str[0]); --i >= 0;) {
1182 			g_regex_unref(xml_decode_regexen[i]);
1183 		}
1184 		g_free(xml_decode_regexen);
1185 		xml_decode_regexen = NULL;
1186 	}
1187 }
1188 
1189 static void
xkl_config_registry_class_init(XklConfigRegistryClass * klass)1190 xkl_config_registry_class_init(XklConfigRegistryClass * klass)
1191 {
1192 	GObjectClass *object_class;
1193 	GParamSpec *engine_param_spec;
1194 	gint i;
1195 	object_class = (GObjectClass *) klass;
1196 	parent_class = g_type_class_peek_parent(object_class);
1197 	object_class->constructor = xkl_config_registry_constructor;
1198 	object_class->finalize = xkl_config_registry_finalize;
1199 	object_class->set_property = xkl_config_registry_set_property;
1200 	object_class->get_property = xkl_config_registry_get_property;
1201 	bind_textdomain_codeset(XKB_DOMAIN, "UTF-8");
1202 	engine_param_spec =
1203 	    g_param_spec_object("engine", "Engine", "XklEngine",
1204 				XKL_TYPE_ENGINE,
1205 				G_PARAM_CONSTRUCT_ONLY |
1206 				G_PARAM_READWRITE);
1207 	g_object_class_install_property(object_class, PROP_ENGINE,
1208 					engine_param_spec);
1209 	/* static stuff initialized */
1210 	xmlXPathInit();
1211 	models_xpath = xmlXPathCompile((unsigned char *)
1212 				       XKBCR_MODEL_PATH);
1213 	layouts_xpath = xmlXPathCompile((unsigned char *)
1214 					XKBCR_LAYOUT_PATH);
1215 	option_groups_xpath = xmlXPathCompile((unsigned char *)
1216 					      XKBCR_GROUP_PATH);
1217 	xml_encode_regexen =
1218 	    g_new0(GRegex *,
1219 		   sizeof(xml_encode_regexen_str) /
1220 		   sizeof(xml_encode_regexen_str[0]));
1221 	xml_decode_regexen =
1222 	    g_new0(GRegex *,
1223 		   sizeof(xml_decode_regexen_str) /
1224 		   sizeof(xml_decode_regexen_str[0]));
1225 	for (i =
1226 	     sizeof(xml_encode_regexen_str) /
1227 	     sizeof(xml_encode_regexen_str[0]); --i >= 0;) {
1228 		xml_encode_regexen[i] =
1229 		    g_regex_new(xml_encode_regexen_str[i], 0, 0, NULL);
1230 		xml_decode_regexen[i] =
1231 		    g_regex_new(xml_decode_regexen_str[i], 0, 0, NULL);
1232 	}
1233 }
1234