1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* enchant
3  * Copyright (C) 2003, 2004 Dom Lachowicz
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, Dom Lachowicz
21  * gives permission to link the code of this program with
22  * non-LGPL Spelling Provider libraries (eg: a MSFT Office
23  * spell checker backend) and distribute linked combinations including
24  * the two.  You must obey the GNU Lesser General Public License in all
25  * respects for all of the code used other than said providers.  If you modify
26  * this file, you may extend this exception to your version of the
27  * file, but you are not obligated to do so.  If you do not wish to
28  * do so, delete this exception statement from your version.
29  */
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <errno.h>
35 
36 #include <glib.h>
37 #include <gmodule.h>
38 #include <glib/gstdio.h>
39 #include <locale.h>
40 
41 #include "enchant.h"
42 #include "enchant-provider.h"
43 #include "pwl.h"
44 
45 #ifdef XP_TARGET_COCOA
46 #import "enchant_cocoa.h"
47 #endif
48 
49 #ifdef XP_TARGET_COCOA
50 #define ENCHANT_USER_PATH_EXTENSION "Library", "Application Support", "Enchant"
51 #elif defined(_WIN32)
52 #define ENCHANT_USER_PATH_EXTENSION "enchant"
53 #else
54 #define ENCHANT_USER_PATH_EXTENSION ".enchant"
55 #endif
56 
57 #ifdef ENABLE_BINRELOC
58 #include "prefix.h"
59 #endif
60 
61 ENCHANT_PLUGIN_DECLARE("Enchant")
62 
63 static char *
64 enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key);
65 
66 /********************************************************************************/
67 /********************************************************************************/
68 
69 struct str_enchant_broker
70 {
71 	GSList *provider_list;	/* list of all of the spelling backend providers */
72 	GHashTable *dict_map;		/* map of language tag -> dictionary */
73 	GHashTable *provider_ordering; /* map of language tag -> provider order */
74 	GHashTable *params;
75 
76 	gchar * error;
77 };
78 
79 typedef struct str_enchant_session
80 {
81 	GHashTable *session_include;
82 	GHashTable *session_exclude;
83 	EnchantPWL *personal;
84 	EnchantPWL *exclude;
85 
86 	char * personal_filename;
87 	char * exclude_filename;
88 	char * language_tag;
89 
90 	char * error;
91 
92 	gboolean is_pwl;
93 
94 	EnchantProvider * provider;
95 } EnchantSession;
96 
97 typedef struct str_enchant_dict_private_data
98 {
99 	unsigned int reference_count;
100 	EnchantSession* session;
101 } EnchantDictPrivateData;
102 
103 typedef EnchantProvider *(*EnchantProviderInitFunc) (void);
104 typedef void             (*EnchantPreConfigureFunc) (EnchantProvider * provider, const char * module_dir);
105 
106 /********************************************************************************/
107 /********************************************************************************/
108 
109 #ifdef _WIN32
110 #define path_cmp g_utf8_collate
111 #else
112 #define path_cmp strcmp
113 #endif
114 
enchant_slist_prepend_unique_path(GSList * slist,gchar * data)115 static GSList* enchant_slist_prepend_unique_path (GSList *slist, gchar* data)
116 {
117 	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
118 		{
119 			return g_slist_prepend (slist, data);
120 		}
121 	else
122 		{
123 			g_free (data);
124 			return slist;
125 		}
126 }
127 
enchant_slist_append_unique_path(GSList * slist,gchar * data)128 static GSList* enchant_slist_append_unique_path (GSList *slist, gchar* data)
129 {
130 	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
131 		{
132 			return g_slist_append (slist, data);
133 		}
134 	else
135 		{
136 			g_free (data);
137 			return slist;
138 		}
139 }
140 
141 static GSList *
_enchant_get_user_home_dirs(void)142 _enchant_get_user_home_dirs (void)
143 {
144 	GSList *dirs = NULL;
145 	const char* home_dir;
146 	char *tmp;
147 
148 	tmp = enchant_get_registry_value_ex (1, "Config", "Home_Dir");
149 	if (tmp)
150 		dirs = enchant_slist_append_unique_path (dirs, tmp);
151 
152 	home_dir = g_get_home_dir ();
153 	if (home_dir)
154 		dirs = enchant_slist_append_unique_path (dirs, g_strdup (home_dir));
155 
156 	return dirs;
157 }
158 
159 static void
_enchant_ensure_dir_exists(const char * dir)160 _enchant_ensure_dir_exists (const char* dir)
161 {
162 	if (dir && !g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
163 		{
164 			(void)g_remove (dir);
165 			g_mkdir_with_parents (dir, 0700);
166 		}
167 }
168 
169 static GSList *
enchant_get_user_dirs(void)170 enchant_get_user_dirs (void)
171 {
172 	GSList *user_dirs = NULL;
173 
174 	{
175 		const char * user_config_dir;
176 
177 		user_config_dir = g_get_user_config_dir();
178 
179 		if (user_config_dir)
180 			user_dirs = enchant_slist_append_unique_path (user_dirs, g_build_filename (user_config_dir,
181 										 "enchant",
182 										 NULL));
183 	}
184 
185 	{
186 		GSList *home_dirs = NULL, *dir;
187 		home_dirs = _enchant_get_user_home_dirs ();
188 
189 		for (dir = home_dirs; dir; dir = dir->next)
190 			{
191 				user_dirs = enchant_slist_append_unique_path (user_dirs,
192 							    g_build_filename (dir->data,
193 									      ENCHANT_USER_PATH_EXTENSION,
194 									      NULL));
195 			}
196 
197 		g_slist_foreach (home_dirs, (GFunc)g_free, NULL);
198 		g_slist_free (home_dirs);
199 	}
200 
201 	return user_dirs;
202 }
203 
204 /* place to look for system level providers */
205 static GSList *
enchant_get_module_dirs(void)206 enchant_get_module_dirs (void)
207 {
208 	GSList *module_dirs = NULL;
209 
210 	char * module_dir = NULL;
211 	char * prefix = NULL;
212 
213 	{
214 		char* user_module_dir;
215 
216 		user_module_dir = enchant_get_registry_value_ex (1, "Config", "Module_Dir");
217 		if (user_module_dir)
218 			module_dirs = enchant_slist_append_unique_path (module_dirs, user_module_dir);
219 	}
220 
221 #ifdef XP_TARGET_COCOA
222 	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup ([[EnchantResourceProvider instance] moduleFolder]));
223 #endif
224 
225 	{
226 		GSList *user_dirs, *iter;
227 
228 		user_dirs = enchant_get_user_dirs();
229 
230 		for (iter = user_dirs; iter; iter = iter->next)
231 			module_dirs = enchant_slist_append_unique_path (module_dirs, iter->data);
232 
233 		g_slist_free (user_dirs);
234 	}
235 
236 	/* Look for explicitly set registry values */
237 	module_dir = enchant_get_registry_value_ex (0, "Config", "Module_Dir");
238 	if (module_dir)
239 		module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);
240 
241 #if defined(ENCHANT_GLOBAL_MODULE_DIR)
242 	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup (ENCHANT_GLOBAL_MODULE_DIR));
243 #else
244 	/* Dynamically locate library and search for modules relative to it. */
245 	prefix = enchant_get_prefix_dir();
246 	if(prefix)
247 		{
248 			module_dir = g_build_filename(prefix,"lib","enchant",NULL);
249 			g_free(prefix);
250 			module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);
251 		}
252 #endif
253 
254 	return module_dirs;
255 }
256 
257 static GSList *
enchant_get_conf_dirs(void)258 enchant_get_conf_dirs (void)
259 {
260 	GSList *conf_dirs = NULL, *user_conf_dirs, *iter;
261 	char * ordering_dir = NULL, * prefix = NULL;
262 
263 	user_conf_dirs = enchant_get_user_config_dirs();
264 
265 	for (iter = user_conf_dirs; iter != NULL; iter = iter->next)
266 		{
267 			conf_dirs = enchant_slist_append_unique_path (conf_dirs, iter->data);
268 		}
269 
270 	g_slist_free (user_conf_dirs);
271 
272 #ifdef XP_TARGET_COCOA
273 	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup ([[EnchantResourceProvider instance] configFolder]));
274 #endif
275 
276 	/* Look for explicitly set registry values */
277 	ordering_dir = enchant_get_registry_value_ex (0, "Config", "Data_Dir");
278 	if (ordering_dir)
279 		conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);
280 
281 	/* Dynamically locate library and search for files relative to it. */
282 	prefix = enchant_get_prefix_dir();
283 	if(prefix)
284 		{
285 			ordering_dir = g_build_filename(prefix,"share","enchant",NULL);
286 			g_free(prefix);
287 			conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);
288 		}
289 
290 #if defined(ENCHANT_GLOBAL_ORDERING)
291 	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup (ENCHANT_GLOBAL_ORDERING));
292 #endif
293 
294 	return conf_dirs;
295 }
296 
297 ENCHANT_MODULE_EXPORT(FILE *)
enchant_fopen(const gchar * filename,const gchar * mode)298 enchant_fopen (const gchar *filename, const gchar *mode)
299 {
300 #ifdef G_OS_WIN32
301   wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
302   wchar_t *wmode;
303   FILE *retval;
304   int save_errno;
305 
306   if (wfilename == NULL)
307     {
308       errno = EINVAL;
309       return NULL;
310     }
311 
312   wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL);
313 
314   if (wmode == NULL)
315     {
316       g_free (wfilename);
317       errno = EINVAL;
318       return NULL;
319     }
320 
321   retval = _wfopen (wfilename, wmode);
322   save_errno = errno;
323 
324   g_free (wfilename);
325   g_free (wmode);
326 
327   errno = save_errno;
328   return retval;
329 #else
330   return fopen (filename, mode);
331 #endif
332 }
333 
334 /**
335  * enchant_get_user_config_dir
336  *
337  * Returns: the user's enchant directory, or %null. Returned value
338  * must be free'd.
339  *
340  * The enchant directory is the place where enchant finds user
341  * dictionaries and settings related to enchant
342  *
343  * This API is private to the providers.
344  */
345 ENCHANT_MODULE_EXPORT (GSList *)
enchant_get_user_config_dirs(void)346 enchant_get_user_config_dirs (void)
347 {
348 	GSList *dirs;
349 	char* user_config;
350 
351 	dirs = enchant_get_user_dirs();
352 
353 	user_config = enchant_get_registry_value_ex (1, "Config", "Data_Dir");
354 	if (user_config)
355 		dirs = enchant_slist_prepend_unique_path (dirs, user_config);
356 
357 	return dirs;
358 }
359 
360 /*
361  * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
362  */
363 static char *
enchant_get_registry_value_ex(int current_user,const char * const prefix,const char * const key)364 enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key)
365 {
366 #ifndef _WIN32
367 	/* TODO: GConf? KConfig? */
368 	return NULL;
369 #else
370 	HKEY hKey;
371 	HKEY baseKey;
372 	unsigned long lType;
373 	DWORD dwSize;
374 	char* keyName;
375 	WCHAR* wszValue = NULL;
376 	char* szValue = NULL;
377 	gunichar2 * uKeyName;
378 	gunichar2 * uKey;
379 
380 	if (current_user)
381 		baseKey = HKEY_CURRENT_USER;
382 	else
383 		baseKey = HKEY_LOCAL_MACHINE;
384 
385 	keyName = g_strdup_printf("Software\\Enchant\\%s", prefix);
386 	uKeyName = g_utf8_to_utf16 (keyName, -1, NULL, NULL, NULL);
387 	uKey = g_utf8_to_utf16 (key, -1, NULL, NULL, NULL);
388 
389 	if(RegOpenKeyExW(baseKey, uKeyName, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
390 		{
391 			/* Determine size of string */
392 			if(RegQueryValueExW( hKey, uKey, NULL, &lType, NULL, &dwSize) == ERROR_SUCCESS)
393 				{
394 					wszValue = g_new0(WCHAR, dwSize + 1);
395 					RegQueryValueExW(hKey, uKey, NULL, &lType, (LPBYTE) wszValue, &dwSize);
396 				}
397 			RegCloseKey(hKey);
398 		}
399 
400 	if(wszValue && *wszValue)
401 		szValue = g_utf16_to_utf8 (wszValue, -1, NULL, NULL, NULL);
402 
403 	g_free(keyName);
404 	g_free(uKeyName);
405 	g_free(uKey);
406 	g_free(wszValue);
407 
408 	return szValue;
409 #endif
410 }
411 
412 /**
413  * enchant_get_registry_value
414  * @prefix: Your category, such as "Ispell" or "Myspell"
415  * @key: The tag within your category that you're interested in
416  *
417  * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
418  *
419  * This API is private to the providers.
420  */
421 ENCHANT_MODULE_EXPORT (char *)
enchant_get_registry_value(const char * const prefix,const char * const key)422 enchant_get_registry_value (const char * const prefix, const char * const key)
423 {
424 	char *val;
425 
426 	g_return_val_if_fail (prefix, NULL);
427 	g_return_val_if_fail (key, NULL);
428 
429 	val = enchant_get_registry_value_ex(1, prefix, key);
430 	if(val == NULL) {
431 			val = enchant_get_registry_value_ex (0, prefix, key);
432 		}
433 	return val;
434 }
435 
436 /********************************************************************************/
437 /********************************************************************************/
438 
439 static gchar*
enchant_modify_string_chars(gchar * str,gssize len,gchar (* function)(gchar))440 enchant_modify_string_chars (gchar *str,
441 							 gssize len,
442 							 gchar (*function)(gchar))
443 {
444 	gchar* it, *end;
445 
446 	g_return_val_if_fail (str != NULL, NULL);
447 
448 	if (len < 0)
449 		len = strlen (str);
450 
451 	end = str + len;
452 
453 	for (it = str; it != end; ++it)
454 		*it = function (*it);
455 
456 	return str;
457 }
458 
459 static gchar*
enchant_ascii_strup(gchar * str,gssize len)460 enchant_ascii_strup (gchar *str,
461 					 gssize len)
462 {
463 	return enchant_modify_string_chars(str, len, g_ascii_toupper);
464 }
465 
466 static gchar*
enchant_ascii_strdown(gchar * str,gssize len)467 enchant_ascii_strdown (gchar *str,
468 						  gssize len)
469 {
470 	return enchant_modify_string_chars(str, len, g_ascii_tolower);
471 }
472 
473 /* returns TRUE if tag is valid
474  * for requires alphanumeric ASCII or underscore
475  */
476 static int
enchant_is_valid_dictionary_tag(const char * const tag)477 enchant_is_valid_dictionary_tag(const char * const tag)
478 {
479 	const char * it;
480 	for (it = tag; *it; ++it)
481 		{
482 			if(!g_ascii_isalnum(*it) && *it != '_')
483 				return 0;
484 		}
485 
486 	return it != tag; /*empty tag invalid*/
487 }
488 
489 static char *
enchant_normalize_dictionary_tag(const char * const dict_tag)490 enchant_normalize_dictionary_tag (const char * const dict_tag)
491 {
492 	char * new_tag = g_strdup (dict_tag);
493 	char * needle;
494 
495 	new_tag = g_strstrip (new_tag);
496 
497 	/* strip off en_GB@euro */
498 	if ((needle = strchr (new_tag, '@')) != NULL)
499 		*needle = '\0';
500 
501 	/* strip off en_GB.UTF-8 */
502 	if ((needle = strchr (new_tag, '.')) != NULL)
503 		*needle = '\0';
504 
505 	/* turn en-GB into en_GB */
506 	if ((needle = strchr (new_tag, '-')) != NULL)
507 		*needle = '_';
508 
509 	/* everything before first '_' is converted to lower case */
510 	if ((needle = strchr (new_tag, '_')) != NULL) {
511 			enchant_ascii_strdown(new_tag, needle - new_tag);
512 			++needle;
513 			/* everything after first '_' is converted to upper case */
514 			enchant_ascii_strup(needle, -1);
515 		}
516 	else {
517 			enchant_ascii_strdown(new_tag, -1);
518 		}
519 
520 	return new_tag;
521 }
522 
523 static char *
enchant_iso_639_from_tag(const char * const dict_tag)524 enchant_iso_639_from_tag (const char * const dict_tag)
525 {
526 	char * new_tag = g_strdup (dict_tag);
527 	char * needle;
528 
529 	if ((needle = strchr (new_tag, '_')) != NULL)
530 		*needle = '\0';
531 
532 	return new_tag;
533 }
534 
535 static void
enchant_session_destroy(EnchantSession * session)536 enchant_session_destroy (EnchantSession * session)
537 {
538 	g_hash_table_destroy (session->session_include);
539 	g_hash_table_destroy (session->session_exclude);
540 	enchant_pwl_free (session->personal);
541 	enchant_pwl_free (session->exclude);
542 	g_free (session->personal_filename);
543 	g_free (session->exclude_filename);
544 	g_free (session->language_tag);
545 
546 	if (session->error)
547 		g_free (session->error);
548 
549 	g_free (session);
550 }
551 
552 static EnchantSession *
enchant_session_new_with_pwl(EnchantProvider * provider,const char * const pwl,const char * const excl,const char * const lang,gboolean fail_if_no_pwl)553 enchant_session_new_with_pwl (EnchantProvider * provider,
554 							  const char * const pwl,
555 							  const char * const excl,
556 							  const char * const lang,
557 							  gboolean fail_if_no_pwl)
558 {
559 	EnchantSession * session;
560 	EnchantPWL *personal = NULL;
561 	EnchantPWL *exclude = NULL;
562 
563 	if (pwl)
564 		personal = enchant_pwl_init_with_file (pwl);
565 
566 	if (personal == NULL) {
567 		if (fail_if_no_pwl)
568 			return NULL;
569 		else
570 			personal = enchant_pwl_init ();
571 	}
572 
573 	if (excl)
574 		exclude = enchant_pwl_init_with_file (excl);
575 	if (exclude == NULL)
576 		exclude = enchant_pwl_init ();
577 
578 	session = g_new0 (EnchantSession, 1);
579 	session->session_include = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
580 	session->session_exclude = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
581 	session->personal = personal;
582 	session->exclude = exclude;
583 	session->provider = provider;
584 	session->language_tag = g_strdup (lang);
585 	session->personal_filename = g_strdup (pwl);
586 	session->exclude_filename = g_strdup (excl);
587 
588 	return session;
589 }
590 
591 static EnchantSession *
_enchant_session_new(EnchantProvider * provider,const char * const user_config_dir,const char * const lang,gboolean fail_if_no_pwl)592 _enchant_session_new (EnchantProvider *provider, const char * const user_config_dir,
593 		      const char * const lang, gboolean fail_if_no_pwl)
594 {
595 	char *filename, *dic, *excl;
596 	EnchantSession * session;
597 
598 	if (!user_config_dir || !lang)
599 		return NULL;
600 
601 	filename = g_strdup_printf ("%s.dic", lang);
602 	dic = g_build_filename (user_config_dir, filename, NULL);
603 	g_free (filename);
604 
605 	filename = g_strdup_printf ("%s.exc", lang);
606 	excl = g_build_filename (user_config_dir, filename,	NULL);
607 	g_free (filename);
608 
609 	session = enchant_session_new_with_pwl (provider, dic, excl, lang, fail_if_no_pwl);
610 
611 	g_free (dic);
612 	g_free (excl);
613 
614 	return session;
615 }
616 
617 static EnchantSession *
enchant_session_new(EnchantProvider * provider,const char * const lang)618 enchant_session_new (EnchantProvider *provider, const char * const lang)
619 {
620 	EnchantSession * session = NULL;
621 	GSList *user_config_dirs, *iter;
622 
623 	user_config_dirs = enchant_get_user_config_dirs ();
624 	for (iter = user_config_dirs; iter != NULL && session == NULL; iter = iter->next)
625 		{
626 			session =_enchant_session_new (provider, iter->data, lang, TRUE);
627 		}
628 
629 	if (session == NULL && user_config_dirs != NULL)
630 		{
631 			_enchant_ensure_dir_exists (user_config_dirs->data);
632 
633 			session =_enchant_session_new (provider, user_config_dirs->data, lang, FALSE);
634 		}
635 
636 	g_slist_foreach (user_config_dirs, (GFunc)g_free, NULL);
637 	g_slist_free (user_config_dirs);
638 
639 
640 	return session;
641 }
642 
643 static void
enchant_session_add(EnchantSession * session,const char * const word,size_t len)644 enchant_session_add (EnchantSession * session, const char * const word, size_t len)
645 {
646 	char* key = g_strndup (word, len);
647 	g_hash_table_remove (session->session_exclude, key);
648 	g_hash_table_insert (session->session_include, key, GINT_TO_POINTER(TRUE));
649 }
650 
651 static void
enchant_session_remove(EnchantSession * session,const char * const word,size_t len)652 enchant_session_remove (EnchantSession * session, const char * const word, size_t len)
653 {
654 	char* key = g_strndup (word, len);
655 	g_hash_table_remove (session->session_include, key);
656 	g_hash_table_insert (session->session_exclude, key, GINT_TO_POINTER(TRUE));
657 }
658 
659 static void
enchant_session_add_personal(EnchantSession * session,const char * const word,size_t len)660 enchant_session_add_personal (EnchantSession * session, const char * const word, size_t len)
661 {
662 	enchant_pwl_add(session->personal, word, len);
663 }
664 
665 static void
enchant_session_remove_personal(EnchantSession * session,const char * const word,size_t len)666 enchant_session_remove_personal (EnchantSession * session, const char * const word, size_t len)
667 {
668 	enchant_pwl_remove(session->personal, word, len);
669 }
670 
671 static void
enchant_session_add_exclude(EnchantSession * session,const char * const word,size_t len)672 enchant_session_add_exclude (EnchantSession * session, const char * const word, size_t len)
673 {
674 	enchant_pwl_add(session->exclude, word, len);
675 }
676 
677 static void
enchant_session_remove_exclude(EnchantSession * session,const char * const word,size_t len)678 enchant_session_remove_exclude (EnchantSession * session, const char * const word, size_t len)
679 {
680 	enchant_pwl_remove(session->exclude, word, len);
681 }
682 
683 /* a word is excluded if it is in the exclude dictionary or in the session exclude list
684  *  AND the word has not been added to the session include list
685  */
686 static gboolean
enchant_session_exclude(EnchantSession * session,const char * const word,size_t len)687 enchant_session_exclude (EnchantSession * session, const char * const word, size_t len)
688 {
689 	gboolean result = FALSE;
690 
691 	char * utf = g_strndup (word, len);
692 
693 	if (!g_hash_table_lookup (session->session_include, utf) &&
694 			(g_hash_table_lookup (session->session_exclude, utf)||
695 			 enchant_pwl_check (session->exclude, word, len) == 0 ))
696 			result = TRUE;
697 	g_free (utf);
698 
699 	return result;
700 }
701 
702 static gboolean
enchant_session_contains(EnchantSession * session,const char * const word,size_t len)703 enchant_session_contains (EnchantSession * session, const char * const word, size_t len)
704 {
705 	gboolean result = FALSE;
706 
707 	char * utf = g_strndup (word, len);
708 
709 	if (g_hash_table_lookup (session->session_include, utf) ||
710 		(enchant_pwl_check (session->personal, word, len) == 0 &&
711 		 !enchant_pwl_check (session->exclude, word, len) == 0))
712 		result = TRUE;
713 
714 	g_free (utf);
715 
716 	return result;
717 }
718 
719 static void
enchant_session_clear_error(EnchantSession * session)720 enchant_session_clear_error (EnchantSession * session)
721 {
722 	if (session->error)
723 		{
724 			g_free (session->error);
725 			session->error = NULL;
726 		}
727 }
728 
729 /********************************************************************************/
730 /********************************************************************************/
731 
732 static void
enchant_provider_free_string_list(EnchantProvider * provider,char ** string_list)733 enchant_provider_free_string_list (EnchantProvider * provider, char ** string_list)
734 {
735 	if (provider && provider->free_string_list)
736 		(*provider->free_string_list) (provider, string_list);
737 }
738 
739 /**
740  * enchant_dict_set_error
741  * @dict: A non-null dictionary
742  * @err: A non-null error message
743  *
744  * Sets the current runtime error to @err. This API is private to the
745  * providers.
746  */
747 ENCHANT_MODULE_EXPORT(void)
enchant_dict_set_error(EnchantDict * dict,const char * const err)748 enchant_dict_set_error (EnchantDict * dict, const char * const err)
749 {
750 	EnchantSession * session;
751 
752 	g_return_if_fail (dict);
753 	g_return_if_fail (err);
754 	g_return_if_fail (g_utf8_validate(err, -1, NULL));
755 
756 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
757 
758 	enchant_session_clear_error (session);
759 	session->error = g_strdup (err);
760 }
761 
762 /**
763  * enchant_dict_get_error
764  * @dict: A non-null dictionary
765  *
766  * Returns a const char string or NULL describing the last exception in UTF8 encoding.
767  * WARNING: error is transient. It will likely be cleared as soon as
768  * the next dictionary operation is called
769  *
770  * Returns: an error message
771  */
772 ENCHANT_MODULE_EXPORT(char *)
enchant_dict_get_error(EnchantDict * dict)773 enchant_dict_get_error (EnchantDict * dict)
774 {
775 	EnchantSession * session;
776 
777 	g_return_val_if_fail (dict, NULL);
778 
779 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
780 	return session->error;
781 }
782 
783 /**
784  * enchant_dict_check
785  * @dict: A non-null #EnchantDict
786  * @word: The non-null word you wish to check, in UTF-8 encoding
787  * @len: The byte length of @word, or -1 for strlen (@word)
788  *
789  * Will return an "incorrect" value if any of those pre-conditions
790  * are not met.
791  *
792  * Returns: 0 if the word is correctly spelled, positive if not, negative if error
793  */
794 ENCHANT_MODULE_EXPORT (int)
enchant_dict_check(EnchantDict * dict,const char * const word,ssize_t len)795 enchant_dict_check (EnchantDict * dict, const char *const word, ssize_t len)
796 {
797 	EnchantSession * session;
798 
799 	g_return_val_if_fail (dict, -1);
800 	g_return_val_if_fail (word, -1);
801 
802 	if (len < 0)
803 		len = strlen (word);
804 
805 	g_return_val_if_fail (len, -1);
806 	g_return_val_if_fail (g_utf8_validate(word, len, NULL),-1);
807 
808 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
809 	enchant_session_clear_error (session);
810 
811 	/* first, see if it's to be excluded*/
812 	if (enchant_session_exclude (session, word, len))
813 		return 1;
814 
815 	/* then, see if it's in our pwl or session*/
816 	if (enchant_session_contains(session, word, len))
817 		return 0;
818 
819 	if (dict->check)
820 		return (*dict->check) (dict, word, len);
821 	else if (session->is_pwl)
822 		return 1;
823 
824 	return -1;
825 }
826 
827 /* @suggs must have at least n_suggs + n_new_suggs space allocated
828  * @n_suggs is the number if items currently appearing in @suggs
829  *
830  * returns the number of items in @suggs after merge is complete
831  */
832 static int
enchant_dict_merge_suggestions(EnchantDict * dict,const char ** suggs,size_t n_suggs,const char * const * const new_suggs,size_t n_new_suggs)833 enchant_dict_merge_suggestions(EnchantDict * dict,
834 								const char ** suggs,
835 								size_t n_suggs,
836 								const char * const* const new_suggs,
837 								size_t n_new_suggs)
838 {
839 	EnchantSession * session;
840 	size_t i, j;
841 
842 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
843 
844 	for(i = 0; i < n_new_suggs; i++)
845 		{
846 			int is_duplicate = 0;
847 			char * normalized_new_sugg;
848 
849 			normalized_new_sugg = g_utf8_normalize (new_suggs[i], -1, G_NORMALIZE_NFD);
850 
851 			for(j = 0; j < n_suggs; j++)
852 				{
853 					char* normalized_sugg;
854 					normalized_sugg = g_utf8_normalize (suggs[j], -1, G_NORMALIZE_NFD);
855 
856 					if(strcmp(normalized_sugg,normalized_new_sugg)==0)
857 						{
858 							is_duplicate = 1;
859 							g_free(normalized_sugg);
860 							break;
861 						}
862 					g_free(normalized_sugg);
863 				}
864 			g_free(normalized_new_sugg);
865 
866 			if(!is_duplicate)
867 				{
868 					suggs[n_suggs] = g_strdup (new_suggs[i]);
869 					++n_suggs;
870 				}
871 		}
872 
873 	return n_suggs;
874 }
875 
876 static char **
enchant_dict_get_good_suggestions(EnchantDict * dict,const char * const * const suggs,size_t n_suggs,size_t * out_n_filtered_suggs)877 enchant_dict_get_good_suggestions(EnchantDict * dict,
878 								const char * const* const suggs,
879 								size_t n_suggs,
880 								size_t* out_n_filtered_suggs)
881 {
882 	EnchantSession * session;
883 	size_t i, n_filtered_suggs;
884 	char ** filtered_suggs;
885 
886 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
887 
888 	filtered_suggs = g_new0 (char *, n_suggs + 1);
889 	n_filtered_suggs = 0;
890 
891 	for(i = 0; i < n_suggs; i++)
892 		{
893 			size_t sugg_len = strlen(suggs[i]);
894 
895 			if(g_utf8_validate(suggs[i], sugg_len, NULL) &&
896 			   !enchant_session_exclude(session, suggs[i], sugg_len) )
897 				{
898 					filtered_suggs[n_filtered_suggs] = g_strdup (suggs[i]);
899 					++n_filtered_suggs;
900 				}
901 		}
902 
903 	if(out_n_filtered_suggs)
904 		*out_n_filtered_suggs = n_filtered_suggs;
905 
906 	return filtered_suggs;
907 }
908 
909 /**
910  * enchant_dict_suggest
911  * @dict: A non-null #EnchantDict
912  * @word: The non-null word you wish to find suggestions for, in UTF-8 encoding
913  * @len: The byte length of @word, or -1 for strlen (@word)
914  * @out_n_suggs: The location to store the # of suggestions returned, or %null
915  *
916  * Will return an %null value if any of those pre-conditions
917  * are not met.
918  *
919  * Returns: A %null terminated list of UTF-8 encoded suggestions, or %null
920  */
921 ENCHANT_MODULE_EXPORT (char **)
enchant_dict_suggest(EnchantDict * dict,const char * const word,ssize_t len,size_t * out_n_suggs)922 enchant_dict_suggest (EnchantDict * dict, const char *const word,
923 			  ssize_t len, size_t * out_n_suggs)
924 {
925 	EnchantSession * session;
926 	size_t n_suggs = 0, n_dict_suggs = 0, n_pwl_suggs = 0, n_suggsT = 0;
927 	char **suggs, **dict_suggs = NULL, **pwl_suggs = NULL, **suggsT;
928 
929 	g_return_val_if_fail (dict, NULL);
930 	g_return_val_if_fail (word, NULL);
931 
932 	if (len < 0)
933 		len = strlen (word);
934 
935 	g_return_val_if_fail (len, NULL);
936 	g_return_val_if_fail (g_utf8_validate(word, len, NULL), NULL);
937 
938 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
939 	enchant_session_clear_error (session);
940 	/* Check for suggestions from provider dictionary */
941 	if (dict->suggest)
942 		{
943 			dict_suggs = (*dict->suggest) (dict, word, len,
944 							&n_dict_suggs);
945 			if(dict_suggs)
946 				{
947 					suggsT = enchant_dict_get_good_suggestions(dict, dict_suggs, n_dict_suggs, &n_suggsT);
948 					enchant_provider_free_string_list (session->provider, dict_suggs);
949 					dict_suggs = suggsT;
950 					n_dict_suggs = n_suggsT;
951 				}
952 		}
953 
954 	/* Check for suggestions from personal dictionary */
955 	if(session->personal)
956 		{
957 			pwl_suggs = enchant_pwl_suggest(session->personal, word, len, dict_suggs, &n_pwl_suggs);
958 			if(pwl_suggs)
959 				{
960 					suggsT = enchant_dict_get_good_suggestions(dict, pwl_suggs, n_pwl_suggs, &n_suggsT);
961 					enchant_pwl_free_string_list (session->personal, pwl_suggs);
962 					pwl_suggs = suggsT;
963 					n_pwl_suggs = n_suggsT;
964 				}
965 		}
966 	/* Clone suggestions if there are any */
967 	n_suggs = n_pwl_suggs + n_dict_suggs;
968 	if (n_suggs > 0)
969 		{
970 			suggs = g_new0 (char *, n_suggs + 1);
971 
972 			/* Copy over suggestions from dict, if no dupes */
973 			n_suggs = enchant_dict_merge_suggestions(dict,
974 								 suggs, 0,
975 								 dict_suggs, n_dict_suggs);
976 
977 			/* Copy over suggestions from pwl, if no dupes */
978 			n_suggs = enchant_dict_merge_suggestions(dict,
979 								 suggs, n_suggs,
980 								 pwl_suggs, n_pwl_suggs);
981 			if(n_suggs == 0)
982 			{
983 				g_free(suggs);
984 				suggs = NULL;
985 			}
986 		}
987 	else
988 		{
989 			suggs = NULL;
990 		}
991 
992 	g_strfreev(dict_suggs);
993 	g_strfreev(pwl_suggs);
994 
995 	if (out_n_suggs)
996 		*out_n_suggs = n_suggs;
997 
998 	return suggs;
999 }
1000 
1001 /**
1002  * enchant_dict_add
1003  * @dict: A non-null #EnchantDict
1004  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1005  * @len: The byte length of @word, or -1 for strlen (@word)
1006  *
1007  * Remarks: if the word exists in the exclude dictionary, it will be removed from the
1008  *          exclude dictionary
1009  */
1010 ENCHANT_MODULE_EXPORT (void)
enchant_dict_add(EnchantDict * dict,const char * const word,ssize_t len)1011 enchant_dict_add (EnchantDict * dict, const char *const word,
1012 			 ssize_t len)
1013 {
1014 	EnchantSession * session;
1015 
1016 	g_return_if_fail (dict);
1017 	g_return_if_fail (word);
1018 
1019 	if (len < 0)
1020 		len = strlen (word);
1021 
1022 	g_return_if_fail (len);
1023 	g_return_if_fail (g_utf8_validate(word, len, NULL));
1024 
1025 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1026 	enchant_session_clear_error (session);
1027 	enchant_session_add_personal (session, word, len);
1028 	enchant_session_remove_exclude (session, word, len);
1029 
1030 	if (dict->add_to_personal)
1031 		(*dict->add_to_personal) (dict, word, len);
1032 }
1033 
1034 /**
1035  * enchant_dict_add_to_pwl
1036  * @dict: A non-null #EnchantDict
1037  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1038  * @len: The byte length of @word, or -1 for strlen (@word)
1039  *
1040  * DEPRECATED. Please use enchant_dict_add() instead.
1041  */
1042 ENCHANT_MODULE_EXPORT (void)
enchant_dict_add_to_pwl(EnchantDict * dict,const char * const word,ssize_t len)1043 enchant_dict_add_to_pwl (EnchantDict * dict, const char *const word,
1044 			 ssize_t len)
1045 {
1046 	enchant_dict_add(dict,word,len);
1047 }
1048 
1049 /**
1050  * enchant_dict_add_to_personal
1051  * @dict: A non-null #EnchantDict
1052  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1053  * @len: The byte length of @word, or -1 for strlen (@word)
1054  *
1055  * DEPRECATED. Please use enchant_dict_add() instead.
1056  */
1057 ENCHANT_MODULE_EXPORT (void)
enchant_dict_add_to_personal(EnchantDict * dict,const char * const word,ssize_t len)1058 enchant_dict_add_to_personal (EnchantDict * dict, const char *const word,
1059 				  ssize_t len)
1060 {
1061 	enchant_dict_add(dict, word, len);
1062 }
1063 
1064 /**
1065  * enchant_dict_add_to_session
1066  * @dict: A non-null #EnchantDict
1067  * @word: The non-null word you wish to add to this spell-checking session, in UTF-8 encoding
1068  * @len: The byte length of @word, or -1 for strlen (@word)
1069  *
1070  */
1071 ENCHANT_MODULE_EXPORT (void)
enchant_dict_add_to_session(EnchantDict * dict,const char * const word,ssize_t len)1072 enchant_dict_add_to_session (EnchantDict * dict, const char *const word,
1073 				 ssize_t len)
1074 {
1075 	EnchantSession * session;
1076 
1077 	g_return_if_fail (dict);
1078 	g_return_if_fail (word);
1079 
1080 	if (len < 0)
1081 		len = strlen (word);
1082 
1083 	g_return_if_fail (len);
1084 	g_return_if_fail (g_utf8_validate(word, len, NULL));
1085 
1086 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1087 	enchant_session_clear_error (session);
1088 
1089 	enchant_session_add (session, word, len);
1090 	if (dict->add_to_session)
1091 		(*dict->add_to_session) (dict, word, len);
1092 }
1093 
1094 /**
1095  * enchant_dict_is_added
1096  * @dict: A non-null #EnchantDict
1097  * @word: The word you wish to see if it has been added (to your session or dict) in UTF8 encoding
1098  * @len: the byte length of @word, or -1 for strlen (@word)
1099  */
1100 ENCHANT_MODULE_EXPORT (int)
enchant_dict_is_added(EnchantDict * dict,const char * const word,ssize_t len)1101 enchant_dict_is_added (EnchantDict * dict, const char *const word,
1102 				ssize_t len)
1103 {
1104 	EnchantSession * session;
1105 
1106 	g_return_val_if_fail (dict, 0);
1107 	g_return_val_if_fail (word, 0);
1108 
1109 	if (len < 0)
1110 		len = strlen (word);
1111 
1112 	g_return_val_if_fail (len, 0);
1113 	g_return_val_if_fail (g_utf8_validate(word, len, NULL), 0);
1114 
1115 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1116 	enchant_session_clear_error (session);
1117 
1118 	return enchant_session_contains (session, word, len);
1119 }
1120 
1121 /**
1122  * enchant_dict_is_in_session
1123  * @dict: A non-null #EnchantDict
1124  * @word: The word you wish to see if it's in your session in UTF8 encoding
1125  * @len: the byte length of @word, or -1 for strlen (@word)
1126  *
1127  * DEPRECATED. Please use enchant_dict_is_added() instead.
1128 */
1129 ENCHANT_MODULE_EXPORT (int)
enchant_dict_is_in_session(EnchantDict * dict,const char * const word,ssize_t len)1130 enchant_dict_is_in_session (EnchantDict * dict, const char *const word,
1131 				ssize_t len)
1132 {
1133 	return enchant_dict_is_added(dict, word, len);
1134 }
1135 
1136 /**
1137  * enchant_dict_remove
1138  * @dict: A non-null #EnchantDict
1139  * @word: The non-null word you wish to add to your exclude dictionary and
1140  *        remove from the personal dictionary, in UTF-8 encoding
1141  * @len: The byte length of @word, or -1 for strlen (@word)
1142  *
1143  */
1144 ENCHANT_MODULE_EXPORT (void)
enchant_dict_remove(EnchantDict * dict,const char * const word,ssize_t len)1145 enchant_dict_remove (EnchantDict * dict, const char *const word,
1146 			 ssize_t len)
1147 {
1148 	EnchantSession * session;
1149 
1150 	g_return_if_fail (dict);
1151 	g_return_if_fail (word);
1152 
1153 	if (len < 0)
1154 		len = strlen (word);
1155 
1156 	g_return_if_fail (len);
1157 	g_return_if_fail (g_utf8_validate(word, len, NULL));
1158 
1159 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1160 	enchant_session_clear_error (session);
1161 
1162 	enchant_session_remove_personal (session, word, len);
1163 	enchant_session_add_exclude(session, word, len);
1164 
1165 	if (dict->add_to_exclude)
1166 		(*dict->add_to_exclude) (dict, word, len);
1167 }
1168 
1169 /**
1170  * enchant_dict_remove_from_session
1171  * @dict: A non-null #EnchantDict
1172  * @word: The non-null word you wish to exclude from this spell-checking session, in UTF-8 encoding
1173  * @len: The byte length of @word, or -1 for strlen (@word)
1174  *
1175  */
1176 ENCHANT_MODULE_EXPORT (void)
enchant_dict_remove_from_session(EnchantDict * dict,const char * const word,ssize_t len)1177 enchant_dict_remove_from_session (EnchantDict * dict, const char *const word,
1178 			 ssize_t len)
1179 {
1180 	EnchantSession * session;
1181 
1182 	g_return_if_fail (dict);
1183 	g_return_if_fail (word);
1184 
1185 	if (len < 0)
1186 		len = strlen (word);
1187 
1188 	g_return_if_fail (len);
1189 	g_return_if_fail (g_utf8_validate(word, len, NULL));
1190 
1191 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1192 	enchant_session_clear_error (session);
1193 
1194 	enchant_session_remove (session, word, len);
1195 }
1196 
1197 /**
1198  * enchant_dict_is_removed
1199  * @dict: A non-null #EnchantDict
1200  * @word: The word you wish to see if it has been removed (from your session or dict) in UTF8 encoding
1201  * @len: the byte length of @word, or -1 for strlen (@word)
1202  */
1203 ENCHANT_MODULE_EXPORT (int)
enchant_dict_is_removed(EnchantDict * dict,const char * const word,ssize_t len)1204 enchant_dict_is_removed (EnchantDict * dict, const char *const word,
1205 				ssize_t len)
1206 {
1207 	EnchantSession * session;
1208 
1209 	g_return_val_if_fail (dict, 0);
1210 	g_return_val_if_fail (word, 0);
1211 
1212 	if (len < 0)
1213 		len = strlen (word);
1214 
1215 	g_return_val_if_fail (len, 0);
1216 	g_return_val_if_fail (g_utf8_validate(word, len, NULL), 0);
1217 
1218 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1219 	enchant_session_clear_error (session);
1220 
1221 	return enchant_session_exclude (session, word, len);
1222 }
1223 
1224 /**
1225  * enchant_dict_store_replacement
1226  * @dict: A non-null #EnchantDict
1227  * @mis: The non-null word you wish to add a correction for, in UTF-8 encoding
1228  * @mis_len: The byte length of @mis, or -1 for strlen (@mis)
1229  * @cor: The non-null correction word, in UTF-8 encoding
1230  * @cor_len: The byte length of @cor, or -1 for strlen (@cor)
1231  *
1232  * Notes that you replaced @mis with @cor, so it's possibly more likely
1233  * that future occurrences of @mis will be replaced with @cor. So it might
1234  * bump @cor up in the suggestion list.
1235  */
1236 ENCHANT_MODULE_EXPORT (void)
enchant_dict_store_replacement(EnchantDict * dict,const char * const mis,ssize_t mis_len,const char * const cor,ssize_t cor_len)1237 enchant_dict_store_replacement (EnchantDict * dict,
1238 				const char *const mis, ssize_t mis_len,
1239 				const char *const cor, ssize_t cor_len)
1240 {
1241 	EnchantSession * session;
1242 
1243 	g_return_if_fail (dict);
1244 	g_return_if_fail (mis);
1245 	g_return_if_fail (cor);
1246 
1247 	if (mis_len < 0)
1248 		mis_len = strlen (mis);
1249 
1250 	if (cor_len < 0)
1251 		cor_len = strlen (cor);
1252 
1253 	g_return_if_fail (mis_len);
1254 	g_return_if_fail (cor_len);
1255 
1256 	g_return_if_fail (g_utf8_validate(mis, mis_len, NULL));
1257 	g_return_if_fail (g_utf8_validate(cor, cor_len, NULL));
1258 
1259 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1260 	enchant_session_clear_error (session);
1261 
1262 	/* if it's not implemented, it's not worth emulating */
1263 	if (dict->store_replacement)
1264 		(*dict->store_replacement) (dict, mis, mis_len, cor, cor_len);
1265 }
1266 
1267 /**
1268  * enchant_dict_free_string_list
1269  * @dict: A non-null #EnchantDict
1270  * @string_list: A non-null string list returned from enchant_dict_suggest
1271  *
1272  * Releases the string list
1273  */
1274 ENCHANT_MODULE_EXPORT (void)
enchant_dict_free_string_list(EnchantDict * dict,char ** string_list)1275 enchant_dict_free_string_list (EnchantDict * dict, char **string_list)
1276 {
1277 	EnchantSession * session;
1278 
1279 	g_return_if_fail (dict);
1280 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1281 	enchant_session_clear_error (session);
1282 	g_strfreev(string_list);
1283 }
1284 
1285 /**
1286  * enchant_dict_free_suggestions
1287  * @dict: A non-null #EnchantDict
1288  * @suggestions: The non-null suggestion list returned by
1289  *               'enchant_dict_suggest'
1290  *
1291  * Releases the suggestions
1292  * This function is DEPRECATED. Please use enchant_dict_free_string_list() instead.
1293  */
1294 ENCHANT_MODULE_EXPORT (void)
enchant_dict_free_suggestions(EnchantDict * dict,char ** suggestions)1295 enchant_dict_free_suggestions (EnchantDict * dict, char **suggestions)
1296 {
1297 	enchant_dict_free_string_list (dict, suggestions);
1298 }
1299 
1300 /**
1301  * enchant_dict_describe
1302  * @broker: A non-null #EnchantDict
1303  * @dict: A non-null #EnchantDictDescribeFn
1304  * @user_data: Optional user-data
1305  *
1306  * Describes an individual dictionary
1307  */
1308 ENCHANT_MODULE_EXPORT (void)
enchant_dict_describe(EnchantDict * dict,EnchantDictDescribeFn fn,void * user_data)1309 enchant_dict_describe (EnchantDict * dict,
1310 			   EnchantDictDescribeFn fn,
1311 			   void * user_data)
1312 {
1313 	EnchantSession * session;
1314 	EnchantProvider * provider;
1315 	GModule *module;
1316 
1317 	const char * tag, * name, * desc, * file;
1318 
1319 	g_return_if_fail (dict);
1320 	g_return_if_fail (fn);
1321 
1322 	session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1323 	enchant_session_clear_error (session);
1324 	provider = session->provider;
1325 
1326 	if (provider)
1327 		{
1328 			module = (GModule *) provider->enchant_private_data;
1329 			file = g_module_name (module);
1330 			name = (*provider->identify) (provider);
1331 			desc = (*provider->describe) (provider);
1332 		}
1333 	else
1334 		{
1335 			file = session->personal_filename;
1336 			name = "Personal Wordlist";
1337 			desc = "Personal Wordlist";
1338 		}
1339 
1340 	tag = session->language_tag;
1341 	(*fn) (tag, name, desc, file, user_data);
1342 }
1343 
1344 /***********************************************************************************/
1345 /***********************************************************************************/
1346 
1347 static void
enchant_broker_clear_error(EnchantBroker * broker)1348 enchant_broker_clear_error (EnchantBroker * broker)
1349 {
1350 	if (broker->error)
1351 		{
1352 			g_free (broker->error);
1353 			broker->error = NULL;
1354 		}
1355 }
1356 
1357 static void
enchant_broker_set_error(EnchantBroker * broker,const char * const err)1358 enchant_broker_set_error (EnchantBroker * broker, const char * const err)
1359 {
1360 	enchant_broker_clear_error (broker);
1361 	broker->error = g_strdup (err);
1362 }
1363 
1364 static int
enchant_provider_is_valid(EnchantProvider * provider)1365 enchant_provider_is_valid(EnchantProvider * provider)
1366 {
1367 	if(provider == NULL)
1368 		{
1369 			g_warning ("EnchantProvider cannot be NULL\n");
1370 			return 0;
1371 		}
1372 
1373 	if(provider->identify == NULL)
1374 		{
1375 			g_warning ("EnchantProvider's identify method cannot be NULL\n");
1376 			return 0;
1377 		}
1378 	else if(!g_utf8_validate((*provider->identify)(provider), -1, NULL))
1379 		{
1380 			g_warning ("EnchantProvider's identify method does not return valid utf8.\n");
1381 			return 0;
1382 		}
1383 
1384 	if(provider->describe == NULL)
1385 		{
1386 			g_warning ("EnchantProvider's describe method cannot be NULL\n");
1387 			return 0;
1388 		}
1389 	else if(!g_utf8_validate((*provider->describe)(provider), -1, NULL))
1390 		{
1391 			g_warning ("EnchantProvider's describe method does not return valid utf8.\n");
1392 			return 0;
1393 		}
1394 
1395 	return 1;
1396 }
1397 
1398 static void
enchant_load_providers_in_dir(EnchantBroker * broker,const char * dir_name)1399 enchant_load_providers_in_dir (EnchantBroker * broker, const char *dir_name)
1400 {
1401 	GModule *module = NULL;
1402 	GDir *dir;
1403 	G_CONST_RETURN char *dir_entry;
1404 	size_t entry_len, g_module_suffix_len;
1405 
1406 	char * filename;
1407 
1408 	EnchantProvider *provider;
1409 	EnchantProviderInitFunc init_func;
1410 	EnchantPreConfigureFunc conf_func;
1411 
1412 	dir = g_dir_open (dir_name, 0, NULL);
1413 	if (!dir)
1414 		return;
1415 
1416 	g_module_suffix_len = strlen (G_MODULE_SUFFIX);
1417 
1418 	while ((dir_entry = g_dir_read_name (dir)) != NULL)
1419 		{
1420 			provider = 0;
1421 
1422 			entry_len = strlen (dir_entry);
1423 			if ((entry_len > g_module_suffix_len) &&
1424 				!strcmp(dir_entry+(entry_len-g_module_suffix_len), G_MODULE_SUFFIX))
1425 				{
1426 #ifdef _WIN32
1427 					/* Suppress error popups for failing to load plugins */
1428 					UINT old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
1429 #endif
1430 					filename = g_build_filename (dir_name, dir_entry, NULL);
1431 
1432 					module = g_module_open (filename, (GModuleFlags) 0);
1433 					if (module)
1434 						{
1435 							if (g_module_symbol
1436 								(module, "init_enchant_provider", (gpointer *) (&init_func))
1437 								&& init_func)
1438 								{
1439 									provider = init_func ();
1440 									if (!enchant_provider_is_valid(provider))
1441 										{
1442 											g_warning ("Error loading plugin: %s's init_enchant_provider returned invalid provider.\n", dir_entry);
1443 											if(provider)
1444 												{
1445 													if(provider->dispose)
1446 														provider->dispose(provider);
1447 
1448 													provider = NULL;
1449 												}
1450 											g_module_close (module);
1451 										}
1452 								}
1453 							else
1454 								{
1455 									g_module_close (module);
1456 								}
1457 						}
1458 					else
1459 						{
1460 							g_warning ("Error loading plugin: %s\n", g_module_error());
1461 						}
1462 
1463 					g_free (filename);
1464 #ifdef _WIN32
1465 					/* Restore the original error mode */
1466 					SetErrorMode(old_error_mode);
1467 #endif
1468 				}
1469 			if (provider)
1470 				{
1471 					/* optional entry point to allow modules to look for associated files
1472 					 */
1473 					if (g_module_symbol
1474 						(module, "configure_enchant_provider", (gpointer *) (&conf_func))
1475 						&& conf_func)
1476 						{
1477 							conf_func (provider, dir_name);
1478 							if (!enchant_provider_is_valid(provider))
1479 								{
1480 									g_warning ("Error loading plugin: %s's configure_enchant_provider modified provider and it is now invalid.\n", dir_entry);
1481 									if(provider->dispose)
1482 										provider->dispose(provider);
1483 
1484 									provider = NULL;
1485 									g_module_close (module);
1486 								}
1487 						}
1488 				}
1489 			if (provider)
1490 				{
1491 					provider->enchant_private_data = (void *) module;
1492 					provider->owner = broker;
1493 					broker->provider_list = g_slist_append (broker->provider_list, (gpointer)provider);
1494 				}
1495 		}
1496 
1497 	g_dir_close (dir);
1498 }
1499 
1500 static void
enchant_load_providers(EnchantBroker * broker)1501 enchant_load_providers (EnchantBroker * broker)
1502 {
1503 	GSList *module_dirs, *iter;
1504 
1505 	module_dirs = enchant_get_module_dirs();
1506 
1507 	for (iter = module_dirs; iter; iter = iter->next)
1508 		{
1509 			enchant_load_providers_in_dir (broker, iter->data);
1510 		}
1511 
1512 	g_slist_foreach (module_dirs, (GFunc)g_free, NULL);
1513 	g_slist_free (module_dirs);
1514 }
1515 
1516 static void
enchant_load_ordering_from_file(EnchantBroker * broker,const char * file)1517 enchant_load_ordering_from_file (EnchantBroker * broker, const char * file)
1518 {
1519 	char line [1024];
1520 	char * tag, * ordering;
1521 
1522 	size_t i, len;
1523 
1524 	FILE * f;
1525 
1526 	f = enchant_fopen (file, "r");
1527 	if (!f)
1528 		return;
1529 
1530 	while (NULL != fgets (line, sizeof(line), f)) {
1531 		for (i = 0, len = strlen(line); i < len && line[i] != ':'; i++)
1532 			;
1533 
1534 		if (i < len)
1535 			{
1536 				tag = g_strndup (line, i);
1537 				ordering = g_strndup (line+(i+1), len - i);
1538 
1539 				enchant_broker_set_ordering (broker, tag, ordering);
1540 
1541 				g_free (tag);
1542 				g_free (ordering);
1543 			}
1544 	}
1545 
1546 	fclose (f);
1547 }
1548 
1549 static void
enchant_load_provider_ordering(EnchantBroker * broker)1550 enchant_load_provider_ordering (EnchantBroker * broker)
1551 {
1552 	GSList *conf_dirs, *iter;
1553 
1554 	broker->provider_ordering = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1555 
1556 	/* we want the user's dirs to show up last, so they override system dirs */
1557 	conf_dirs = g_slist_reverse (enchant_get_conf_dirs ());
1558 	for (iter = conf_dirs; iter; iter = iter->next)
1559 		{
1560 			char *ordering_file;
1561 			ordering_file = g_build_filename (iter->data, "enchant1.ordering", NULL);
1562 			enchant_load_ordering_from_file (broker, ordering_file);
1563 			g_free (ordering_file);
1564 		}
1565 
1566 	g_slist_foreach (conf_dirs, (GFunc)g_free, NULL);
1567 	g_slist_free (conf_dirs);
1568 }
1569 
1570 static GSList *
enchant_get_ordered_providers(EnchantBroker * broker,const char * const tag)1571 enchant_get_ordered_providers (EnchantBroker * broker,
1572 				   const char * const tag)
1573 {
1574 	EnchantProvider *provider;
1575 	GSList * list = NULL, * iter = NULL;
1576 
1577 	char * ordering = NULL, ** tokens, *token;
1578 	size_t i;
1579 
1580 	ordering = (char *)g_hash_table_lookup (broker->provider_ordering, (gpointer)tag);
1581 	if (!ordering)
1582 		ordering = (char *)g_hash_table_lookup (broker->provider_ordering, (gpointer)"*");
1583 
1584 	if (!ordering)
1585 		{
1586 			/* return an unordered copy of the list */
1587 			for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter))
1588 					list = g_slist_append (list, iter->data);
1589 			return list;
1590 		}
1591 
1592 	tokens = g_strsplit (ordering, ",", 0);
1593 	if (tokens)
1594 		{
1595 			for (i = 0; tokens[i]; i++)
1596 				{
1597 					token = g_strstrip(tokens[i]);
1598 
1599 					for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter))
1600 						{
1601 							provider = (EnchantProvider*)iter->data;
1602 
1603 							if (provider && !strcmp (token, (*provider->identify)(provider)))
1604 								list = g_slist_append (list, (gpointer)provider);
1605 						}
1606 				}
1607 
1608 			g_strfreev (tokens);
1609 		}
1610 
1611 	/* providers not in the list need to be appended at the end */
1612 	for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter))
1613 		{
1614 			if (!g_slist_find (list, iter->data))
1615 				list = g_slist_append (list, iter->data);
1616 		}
1617 
1618 	return list;
1619 }
1620 
1621 static void
enchant_dict_destroyed(gpointer data)1622 enchant_dict_destroyed (gpointer data)
1623 {
1624 	EnchantDict *dict;
1625 	EnchantProvider *owner;
1626 	EnchantSession *session;
1627 	EnchantDictPrivateData *enchant_dict_private_data;
1628 
1629 	g_return_if_fail (data);
1630 
1631 	dict = (EnchantDict *) data;
1632 	enchant_dict_private_data = (EnchantDictPrivateData*)dict->enchant_private_data;
1633 	session = enchant_dict_private_data->session;
1634 	owner = session->provider;
1635 
1636 	if (owner && owner->dispose_dict)
1637 		(*owner->dispose_dict) (owner, dict);
1638 	else if(session->is_pwl)
1639 		g_free (dict);
1640 
1641 	g_free(enchant_dict_private_data);
1642 
1643 	enchant_session_destroy (session);
1644 }
1645 
1646 static void
enchant_provider_free(gpointer data,gpointer user_data)1647 enchant_provider_free (gpointer data, gpointer user_data)
1648 {
1649 	EnchantProvider *provider;
1650 	GModule *module;
1651 
1652 	g_return_if_fail (data);
1653 
1654 	provider = (EnchantProvider *) data;
1655 	module = (GModule *) provider->enchant_private_data;
1656 
1657 	if (provider->dispose)
1658 		(*provider->dispose) (provider);
1659 
1660 	/* close module only after invoking dispose */
1661 	g_module_close (module);
1662 }
1663 
1664 /**
1665  * enchant_broker_init
1666  *
1667  * Returns: A new broker object capable of requesting
1668  * dictionaries from
1669  */
1670 ENCHANT_MODULE_EXPORT (EnchantBroker *)
enchant_broker_init(void)1671 enchant_broker_init (void)
1672 {
1673 	EnchantBroker *broker = NULL;
1674 
1675 	g_return_val_if_fail (g_module_supported (), NULL);
1676 
1677 #ifdef ENABLE_BINRELOC
1678 	{
1679 		static gboolean binreloc_initialized = FALSE;
1680 
1681 		if (!binreloc_initialized)
1682 			{
1683 				(void)gbr_init_lib (NULL);
1684 				binreloc_initialized = TRUE;
1685 			}
1686 	}
1687 #endif
1688 
1689 	broker = g_new0 (EnchantBroker, 1);
1690 
1691 	broker->dict_map = g_hash_table_new_full (g_str_hash, g_str_equal,
1692 						  g_free, enchant_dict_destroyed);
1693 	broker->params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1694 	enchant_load_providers (broker);
1695 	enchant_load_provider_ordering (broker);
1696 
1697 	return broker;
1698 }
1699 
1700 /**
1701  * enchant_broker_free
1702  * @broker: A non-null #EnchantBroker
1703  *
1704  * Destroys the broker object. Must only be called once per broker init
1705  */
1706 ENCHANT_MODULE_EXPORT (void)
enchant_broker_free(EnchantBroker * broker)1707 enchant_broker_free (EnchantBroker * broker)
1708 {
1709 	guint n_remaining;
1710 
1711 	g_return_if_fail (broker);
1712 
1713 	n_remaining = g_hash_table_size (broker->dict_map);
1714 	if (n_remaining)
1715 		{
1716 			g_warning ("%u dictionaries weren't free'd.\n", n_remaining);
1717 		}
1718 
1719 	/* will destroy any remaining dictionaries for us */
1720 	g_hash_table_destroy (broker->dict_map);
1721 	g_hash_table_destroy (broker->provider_ordering);
1722 	g_hash_table_destroy (broker->params);
1723 
1724 	g_slist_foreach (broker->provider_list, enchant_provider_free, NULL);
1725 	g_slist_free (broker->provider_list);
1726 
1727 	enchant_broker_clear_error (broker);
1728 
1729 	g_free (broker);
1730 }
1731 
1732 /**
1733  * enchant_broker_request_pwl_dict
1734  *
1735  * PWL is a personal wordlist file, 1 entry per line
1736  *
1737  * @pwl: A non-null pathname in the GLib file name encoding (UTF-8 on Windows)
1738  *       to the personal wordlist file
1739  *
1740  * Returns: An EnchantDict. This dictionary is reference counted.
1741  */
1742 ENCHANT_MODULE_EXPORT (EnchantDict *)
enchant_broker_request_pwl_dict(EnchantBroker * broker,const char * const pwl)1743 enchant_broker_request_pwl_dict (EnchantBroker * broker, const char *const pwl)
1744 {
1745 	EnchantSession *session;
1746 	EnchantDictPrivateData *enchant_dict_private_data;
1747 	EnchantDict *dict = NULL;
1748 
1749 	g_return_val_if_fail (broker, NULL);
1750 	g_return_val_if_fail (pwl && strlen(pwl), NULL);
1751 
1752 	enchant_broker_clear_error (broker);
1753 
1754 	dict = (EnchantDict*)g_hash_table_lookup (broker->dict_map, (gpointer) pwl);
1755 	if (dict) {
1756 		((EnchantDictPrivateData*)dict->enchant_private_data)->reference_count++;
1757 		return dict;
1758 	}
1759 
1760 	/* since the broker pwl file is a read/write file (there is no readonly dictionary associated)
1761 	 * there is no need for complementary exclude file to add a word to. The word just needs to be
1762 	 * removed from the broker pwl file
1763 	 */
1764 	session = enchant_session_new_with_pwl (NULL, pwl, NULL, "Personal Wordlist", TRUE);
1765 	if (!session)
1766 		{
1767 			broker->error = g_strdup_printf ("Couldn't open personal wordlist '%s'", pwl);
1768 			return NULL;
1769 		}
1770 
1771 	session->is_pwl = 1;
1772 
1773 	dict = g_new0 (EnchantDict, 1);
1774 	enchant_dict_private_data = g_new0 (EnchantDictPrivateData, 1);
1775 	enchant_dict_private_data->reference_count = 1;
1776 	enchant_dict_private_data->session = session;
1777 	dict->enchant_private_data = (void *)enchant_dict_private_data;
1778 
1779 
1780 	g_hash_table_insert (broker->dict_map, (gpointer)g_strdup (pwl), dict);
1781 
1782 	return dict;
1783 }
1784 
1785 static EnchantDict *
_enchant_broker_request_dict(EnchantBroker * broker,const char * const tag)1786 _enchant_broker_request_dict (EnchantBroker * broker, const char *const tag)
1787 {
1788 	EnchantDict * dict;
1789 	GSList * list;
1790 	GSList * listIter;
1791 
1792 	dict = (EnchantDict*)g_hash_table_lookup (broker->dict_map, (gpointer) tag);
1793 	if (dict) {
1794 		((EnchantDictPrivateData*)dict->enchant_private_data)->reference_count++;
1795 		return dict;
1796 	}
1797 
1798 	list = enchant_get_ordered_providers (broker, tag);
1799 	for (listIter = list; listIter != NULL; listIter = g_slist_next (listIter))
1800 		{
1801 			EnchantProvider * provider;
1802 
1803 			provider = (EnchantProvider *) listIter->data;
1804 
1805 			if (provider->request_dict)
1806 				{
1807 					dict = (*provider->request_dict) (provider, tag);
1808 
1809 					if (dict)
1810 						{
1811 							EnchantSession *session;
1812 							EnchantDictPrivateData *enchant_dict_private_data;
1813 
1814 							session = enchant_session_new (provider, tag);
1815 							enchant_dict_private_data = g_new0 (EnchantDictPrivateData, 1);
1816 							enchant_dict_private_data->reference_count = 1;
1817 							enchant_dict_private_data->session = session;
1818 							dict->enchant_private_data = (void *)enchant_dict_private_data;
1819 							g_hash_table_insert (broker->dict_map, (gpointer)g_strdup (tag), dict);
1820 							break;
1821 						}
1822 				}
1823 		}
1824 
1825 	g_slist_free (list);
1826 
1827 	return dict;
1828 }
1829 
1830 /**
1831  * enchant_broker_request_dict
1832  * @broker: A non-null #EnchantBroker
1833  * @tag: The non-null language tag you wish to request a dictionary for ("en_US", "de_DE", ...)
1834  *
1835  * Returns: An #EnchantDict, or %null if no suitable dictionary could be found. This dictionary is reference counted.
1836  */
1837 ENCHANT_MODULE_EXPORT (EnchantDict *)
enchant_broker_request_dict(EnchantBroker * broker,const char * const tag)1838 enchant_broker_request_dict (EnchantBroker * broker, const char *const tag)
1839 {
1840 	EnchantDict *dict = NULL;
1841 	char * normalized_tag;
1842 
1843 	g_return_val_if_fail (broker, NULL);
1844 	g_return_val_if_fail (tag && strlen(tag), NULL);
1845 
1846 	enchant_broker_clear_error (broker);
1847 
1848 	normalized_tag = enchant_normalize_dictionary_tag (tag);
1849 	if(!enchant_is_valid_dictionary_tag(normalized_tag))
1850 		{
1851 			enchant_broker_set_error (broker, "invalid tag character found");
1852 		}
1853 	else if ((dict = _enchant_broker_request_dict (broker, normalized_tag)) == NULL)
1854 		{
1855 			char * iso_639_only_tag;
1856 
1857 			iso_639_only_tag = enchant_iso_639_from_tag (normalized_tag);
1858 
1859 			dict = _enchant_broker_request_dict (broker, iso_639_only_tag);
1860 
1861 			g_free (iso_639_only_tag);
1862 		}
1863 
1864 	g_free (normalized_tag);
1865 
1866 	return dict;
1867 }
1868 
1869 /**
1870  * enchant_broker_describe
1871  * @broker: A non-null #EnchantBroker
1872  * @fn: A non-null #EnchantBrokerDescribeFn
1873  * @user_data: Optional user-data
1874  *
1875  * Enumerates the Enchant providers and tells
1876  * you some rudimentary information about them.
1877  */
1878 ENCHANT_MODULE_EXPORT (void)
enchant_broker_describe(EnchantBroker * broker,EnchantBrokerDescribeFn fn,void * user_data)1879 enchant_broker_describe (EnchantBroker * broker,
1880 			 EnchantBrokerDescribeFn fn,
1881 			 void * user_data)
1882 {
1883 	GSList *list;
1884 	EnchantProvider *provider;
1885 	GModule *module;
1886 
1887 	const char * name, * desc, * file;
1888 
1889 	g_return_if_fail (broker);
1890 	g_return_if_fail (fn);
1891 
1892 	enchant_broker_clear_error (broker);
1893 
1894 	for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
1895 		{
1896 			provider = (EnchantProvider *) list->data;
1897 			module = (GModule *) provider->enchant_private_data;
1898 
1899 			name = (*provider->identify) (provider);
1900 			desc = (*provider->describe) (provider);
1901 			file = g_module_name (module);
1902 
1903 			(*fn) (name, desc, file, user_data);
1904 		}
1905 }
1906 
1907 /**
1908  * enchant_broker_list_dicts
1909  * @broker: A non-null #EnchantBroker
1910  * @fn: A non-null #EnchantDictDescribeFn
1911  * @user_data: Optional user-data
1912  *
1913  * Enumerates the dictionaries available from
1914  * all Enchant providers.
1915  */
1916 ENCHANT_MODULE_EXPORT (void)
enchant_broker_list_dicts(EnchantBroker * broker,EnchantDictDescribeFn fn,void * user_data)1917 enchant_broker_list_dicts (EnchantBroker * broker,
1918 			   EnchantDictDescribeFn fn,
1919 			   void * user_data)
1920 {
1921 	GSList *list;
1922 	GHashTable *tags;
1923 
1924 	g_return_if_fail (broker);
1925 	g_return_if_fail (fn);
1926 
1927 	tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1928 
1929 	enchant_broker_clear_error (broker);
1930 
1931 	for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
1932 		{
1933 			EnchantProvider *provider;
1934 			GModule *module;
1935 
1936 			provider = (EnchantProvider *) list->data;
1937 			module = (GModule *) provider->enchant_private_data;
1938 
1939 			if (provider->list_dicts)
1940 				{
1941 					const char * tag, * name, * desc, * file;
1942 					size_t n_dicts, i;
1943 					char ** dicts;
1944 
1945 					dicts = (*provider->list_dicts) (provider, &n_dicts);
1946 					name = (*provider->identify) (provider);
1947 					desc = (*provider->describe) (provider);
1948 					file = g_module_name (module);
1949 
1950 					for (i = 0; i < n_dicts; i++)
1951 						{
1952 							tag = dicts[i];
1953 							if(enchant_is_valid_dictionary_tag(tag) &&
1954 							   !g_hash_table_lookup (tags, tag))
1955 								{
1956 									g_hash_table_insert (tags, g_strdup (tag), GINT_TO_POINTER(TRUE));
1957 									(*fn) (tag, name, desc, file, user_data);
1958 								}
1959 						}
1960 
1961 					enchant_provider_free_string_list (provider, dicts);
1962 				}
1963 		}
1964 
1965 	g_hash_table_destroy (tags);
1966 }
1967 
1968 /**
1969  * enchant_broker_free_dict
1970  * @broker: A non-null #EnchantBroker
1971  * @dict: A non-null #EnchantDict
1972  *
1973  * Releases the dictionary when you are done using it. Must only be called once per dictionary request
1974  */
1975 ENCHANT_MODULE_EXPORT (void)
enchant_broker_free_dict(EnchantBroker * broker,EnchantDict * dict)1976 enchant_broker_free_dict (EnchantBroker * broker, EnchantDict * dict)
1977 {
1978 	EnchantSession * session;
1979 	EnchantDictPrivateData * dict_private_data;
1980 
1981 	g_return_if_fail (broker);
1982 	g_return_if_fail (dict);
1983 
1984 	enchant_broker_clear_error (broker);
1985 
1986 	dict_private_data = (EnchantDictPrivateData*)dict->enchant_private_data;
1987 	dict_private_data->reference_count--;
1988 	if(dict_private_data->reference_count == 0)
1989 		{
1990 			session = dict_private_data->session;
1991 
1992 			if (session->provider)
1993 				g_hash_table_remove (broker->dict_map, session->language_tag);
1994 			else
1995 				g_hash_table_remove (broker->dict_map, session->personal_filename);
1996 		}
1997 }
1998 
1999 static int
_enchant_provider_dictionary_exists(EnchantProvider * provider,const char * const tag)2000 _enchant_provider_dictionary_exists (EnchantProvider * provider,
2001 					 const char * const tag)
2002 {
2003 	int exists = 0;
2004 
2005 	if (provider->dictionary_exists)
2006 		{
2007 			exists = (*provider->dictionary_exists) (provider, tag);
2008 		}
2009 	else if (provider->list_dicts)
2010 		{
2011 			size_t n_dicts, i;
2012 			char ** dicts;
2013 
2014 			dicts = (*provider->list_dicts) (provider, &n_dicts);
2015 
2016 			for (i = 0; (i < n_dicts) && !exists; i++)
2017 				{
2018 					if (!strcmp(dicts[i], tag))
2019 						exists = 1;
2020 				}
2021 
2022 			enchant_provider_free_string_list (provider, dicts);
2023 		}
2024 	else if (provider->request_dict)
2025 		{
2026 			EnchantDict *dict;
2027 
2028 			dict = (*provider->request_dict) (provider, tag);
2029 			if (dict)
2030 				{
2031 					if (provider->dispose_dict)
2032 						(*provider->dispose_dict) (provider, dict);
2033 					exists = 1;
2034 				}
2035 		}
2036 
2037 	return exists;
2038 }
2039 
2040 static int
_enchant_broker_dict_exists(EnchantBroker * broker,const char * const tag)2041 _enchant_broker_dict_exists (EnchantBroker * broker,
2042 				 const char * const tag)
2043 {
2044 	GSList * list;
2045 
2046 	/* don't query the providers if it is an empty string */
2047 	if (tag == NULL || *tag == '\0') {
2048 		return 0;
2049 	}
2050 
2051 	/* don't query the providers if we can just do a quick map lookup */
2052 	if (g_hash_table_lookup (broker->dict_map, (gpointer) tag) != NULL) {
2053 		return 1;
2054 	}
2055 
2056 	for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
2057 		{
2058 			EnchantProvider * provider;
2059 
2060 			provider = (EnchantProvider *) list->data;
2061 
2062 			if (_enchant_provider_dictionary_exists (provider, tag))
2063 				{
2064 					return 1;
2065 				}
2066 		}
2067 
2068 	return 0;
2069 }
2070 
2071 /**
2072  * enchant_broker_dict_exists
2073  * @broker: A non-null #EnchantBroker
2074  * @tag: The non-null language tag you wish to request a dictionary for ("en_US", "de_DE", ...)
2075  *
2076  * Return existance of the requested dictionary (1 == true, 0 == false)
2077  */
2078 ENCHANT_MODULE_EXPORT (int)
enchant_broker_dict_exists(EnchantBroker * broker,const char * const tag)2079 enchant_broker_dict_exists (EnchantBroker * broker,
2080 				const char * const tag)
2081 {
2082 	char * normalized_tag;
2083 	int exists = 0;
2084 
2085 	g_return_val_if_fail (broker, 0);
2086 	g_return_val_if_fail (tag && strlen(tag), 0);
2087 
2088 	enchant_broker_clear_error (broker);
2089 
2090 	normalized_tag = enchant_normalize_dictionary_tag (tag);
2091 
2092 	if(!enchant_is_valid_dictionary_tag(normalized_tag))
2093 		{
2094 			enchant_broker_set_error (broker, "invalid tag character found");
2095 		}
2096 	else if ((exists = _enchant_broker_dict_exists (broker, normalized_tag)) == 0)
2097 		{
2098 			char * iso_639_only_tag;
2099 
2100 			iso_639_only_tag = enchant_iso_639_from_tag (normalized_tag);
2101 
2102 			if (strcmp (normalized_tag, iso_639_only_tag) != 0)
2103 				{
2104 					exists = _enchant_broker_dict_exists (broker, iso_639_only_tag);
2105 				}
2106 
2107 			g_free (iso_639_only_tag);
2108 		}
2109 
2110 	g_free (normalized_tag);
2111 	return exists;
2112 }
2113 
2114 /**
2115  * enchant_broker_set_ordering
2116  * @broker: A non-null #EnchantBroker
2117  * @tag: A non-null language tag (en_US)
2118  * @ordering: A non-null ordering (aspell,myspell,ispell,uspell,hspell)
2119  *
2120  * Declares a preference of dictionaries to use for the language
2121  * described/referred to by @tag. The ordering is a comma delimited
2122  * list of provider names. As a special exception, the "*" tag can
2123  * be used as a language tag to declare a default ordering for any
2124  * language that does not explictly declare an ordering.
2125  */
2126 ENCHANT_MODULE_EXPORT (void)
enchant_broker_set_ordering(EnchantBroker * broker,const char * const tag,const char * const ordering)2127 enchant_broker_set_ordering (EnchantBroker * broker,
2128 				 const char * const tag,
2129 				 const char * const ordering)
2130 {
2131 	char * tag_dupl;
2132 	char * ordering_dupl;
2133 
2134 	g_return_if_fail (broker);
2135 	g_return_if_fail (tag && strlen(tag));
2136 	g_return_if_fail (ordering && strlen(ordering));
2137 
2138 	enchant_broker_clear_error (broker);
2139 
2140 	tag_dupl = enchant_normalize_dictionary_tag (tag);
2141 
2142 	ordering_dupl = g_strdup (ordering);
2143 	ordering_dupl = g_strstrip (ordering_dupl);
2144 
2145 	if (tag_dupl && strlen(tag_dupl) &&
2146 		ordering_dupl && strlen(ordering_dupl))
2147 		{
2148 			/* we will free ordering_dupl && tag_dupl when the hash is destroyed */
2149 			g_hash_table_insert (broker->provider_ordering, (gpointer)tag_dupl,
2150 						 (gpointer)(ordering_dupl));
2151 		}
2152 	else
2153 		{
2154 			g_free (tag_dupl);
2155 			g_free (ordering_dupl);
2156 		}
2157 }
2158 
2159 /**
2160  * enchant_provider_set_error
2161  * @provider: A non-null provider
2162  * @err: A non-null error message
2163  *
2164  * Sets the current runtime error to @err. This API is private to
2165  * the providers.
2166  */
2167 ENCHANT_MODULE_EXPORT(void)
enchant_provider_set_error(EnchantProvider * provider,const char * const err)2168 enchant_provider_set_error (EnchantProvider * provider, const char * const err)
2169 {
2170 	EnchantBroker * broker;
2171 
2172 	g_return_if_fail (provider);
2173 	g_return_if_fail (err);
2174 	g_return_if_fail (g_utf8_validate(err, -1, NULL));
2175 
2176 	broker = provider->owner;
2177 	g_return_if_fail (broker);
2178 
2179 	enchant_broker_set_error (broker, err);
2180 }
2181 
2182 /**
2183  * enchant_broker_get_error
2184  * @broker: A non-null broker
2185  *
2186  * Returns a const char string or NULL describing the last exception in UTF8 encoding.
2187  * WARNING: error is transient and is likely cleared as soon as the
2188  * next broker operation happens
2189  */
2190 ENCHANT_MODULE_EXPORT(char *)
enchant_broker_get_error(EnchantBroker * broker)2191 enchant_broker_get_error (EnchantBroker * broker)
2192 {
2193 	g_return_val_if_fail (broker, NULL);
2194 
2195 	return broker->error;
2196 }
2197 
2198 /* private. returned string should be free'd with g_free */
2199 ENCHANT_MODULE_EXPORT(char *)
enchant_get_user_language(void)2200 enchant_get_user_language(void)
2201 {
2202 	char * locale = NULL;
2203 
2204 #if defined(G_OS_WIN32)
2205 	if(!locale)
2206 		locale = g_win32_getlocale ();
2207 #endif
2208 
2209 	if(!locale)
2210 		locale = g_strdup (g_getenv ("LANG"));
2211 
2212 #if defined(HAVE_LC_MESSAGES)
2213 	if(!locale)
2214 		locale = g_strdup (setlocale (LC_MESSAGES, NULL));
2215 #endif
2216 
2217 	if(!locale)
2218 		locale = g_strdup (setlocale (LC_ALL, NULL));
2219 
2220 	if(!locale || strcmp(locale, "C") == 0) {
2221 		g_free(locale);
2222 		locale = g_strdup("en");
2223 	}
2224 
2225 	return locale;
2226 }
2227 
2228 
2229 /**
2230  * enchant_get_prefix_dir
2231  *
2232  * Returns a string giving the location of the base directory
2233  * of the enchant installation.  This corresponds roughly to
2234  * the --prefix option given to ./configure when enchant is
2235  * compiled, except it is determined at runtime based on the location
2236  * of the enchant library.
2237  *
2238  * Returns: the prefix dir if it can be determined, or %null otherwise. Must be free'd.
2239  *
2240  * This API is private to the providers.
2241  *
2242  */
2243 ENCHANT_MODULE_EXPORT (char *)
enchant_get_prefix_dir(void)2244 enchant_get_prefix_dir(void)
2245 {
2246 	char * prefix = NULL;
2247 
2248 #ifdef _WIN32
2249 	if (!prefix) {
2250 		/* Dynamically locate library and return containing directory */
2251 		WCHAR dll_path[MAX_PATH];
2252 
2253 		if(GetModuleFileNameW(s_hModule,dll_path,MAX_PATH))
2254 			{
2255 				gchar* utf8_dll_path = g_utf16_to_utf8 (dll_path, -1, NULL, NULL, NULL);
2256 				prefix = g_path_get_dirname(utf8_dll_path);
2257 				g_free(utf8_dll_path);
2258 				/* Strip off "bin" subfolder if present */
2259 				if (strlen(prefix) >=6 &&
2260 				    G_IS_DIR_SEPARATOR(prefix[strlen(prefix)-4]) &&
2261 				    g_ascii_strcasecmp(prefix+strlen(prefix)-3, "bin") == 0)
2262 					prefix[strlen(prefix)-4] = '\0';
2263 			}
2264 	}
2265 #endif
2266 
2267 #if defined(ENABLE_BINRELOC)
2268 	if (!prefix) {
2269 		/* Use standard binreloc PREFIX macro */
2270 		prefix = gbr_find_prefix(NULL);
2271 	}
2272 #endif
2273 
2274 #if defined(ENCHANT_PREFIX_DIR)
2275 	if (!prefix) {
2276 		prefix = g_strdup (ENCHANT_PREFIX_DIR);
2277 	}
2278 #endif
2279 
2280 	return prefix;
2281 }
2282 
2283 ENCHANT_MODULE_EXPORT(char *)
enchant_broker_get_param(EnchantBroker * broker,const char * const param_name)2284 enchant_broker_get_param (EnchantBroker * broker, const char * const param_name)
2285 {
2286 	g_return_val_if_fail (broker, NULL);
2287 	g_return_val_if_fail (param_name && *param_name, NULL);
2288 
2289 	return g_hash_table_lookup (broker->params, param_name);
2290 }
2291 
2292 ENCHANT_MODULE_EXPORT(void)
enchant_broker_set_param(EnchantBroker * broker,const char * const param_name,const char * const param_value)2293 enchant_broker_set_param (EnchantBroker * broker, const char * const param_name, const char * const param_value)
2294 {
2295 	g_return_if_fail (broker);
2296 	g_return_if_fail (param_name && *param_name);
2297 
2298 	if (param_value == NULL || *param_value == '\0')
2299 		g_hash_table_remove (broker->params, param_name);
2300 	else
2301 		g_hash_table_insert (broker->params, g_strdup (param_name), g_strdup (param_value));
2302 }
2303 
2304 ENCHANT_MODULE_EXPORT (GSList *)
enchant_get_dirs_from_param(EnchantBroker * broker,const char * const param_name)2305 enchant_get_dirs_from_param (EnchantBroker * broker, const char * const param_name)
2306 {
2307 	const char *param_value;
2308 	char **tokens;
2309 	GSList *dirs = NULL;
2310 
2311 	param_value = enchant_broker_get_param (broker, param_name);
2312 	if (param_value == NULL)
2313 		return NULL;
2314 
2315 #ifdef _WIN32
2316 	tokens = g_strsplit (param_value, ";", 0);
2317 #else
2318 	tokens = g_strsplit (param_value, ":", 0);
2319 #endif
2320 	if (tokens != NULL) {
2321 		int i;
2322 		for (i = 0; tokens[i]; i++)
2323 			{
2324 				char *token = g_strstrip(tokens[i]);
2325 				dirs = g_slist_append (dirs, g_strdup (token));
2326 			}
2327 
2328 		g_strfreev (tokens);
2329 	}
2330 
2331 	return dirs;
2332 }
2333 
2334 ENCHANT_MODULE_EXPORT(char *)
enchant_get_version(void)2335 enchant_get_version (void) {
2336 	return ENCHANT_VERSION_STRING;
2337 }
2338