Tizen 2.1 base
[external/enchant.git] / src / enchant.c
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
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
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 *
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
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 *
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 *
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 *
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 *)
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 *)
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 *
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 *)
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*
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*
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*
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
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 *
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 *
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
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 *
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 *
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 *
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
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
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
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
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
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
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
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
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
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
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)
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 *)
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)
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
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 **
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 (sugg_len == 0) continue;
896
897                         if(g_utf8_validate(suggs[i], sugg_len, NULL) && 
898                            !enchant_session_exclude(session, suggs[i], sugg_len) )
899                                 {
900                                         filtered_suggs[n_filtered_suggs] = g_strdup (suggs[i]);
901                                         ++n_filtered_suggs;
902                                 }
903                 }
904
905         if(out_n_filtered_suggs)
906                 *out_n_filtered_suggs = n_filtered_suggs;
907
908         return filtered_suggs;
909 }
910
911 /**
912  * enchant_dict_suggest
913  * @dict: A non-null #EnchantDict
914  * @word: The non-null word you wish to find suggestions for, in UTF-8 encoding
915  * @len: The byte length of @word, or -1 for strlen (@word)
916  * @out_n_suggs: The location to store the # of suggestions returned, or %null
917  *
918  * Will return an %null value if any of those pre-conditions
919  * are not met.
920  *
921  * Returns: A %null terminated list of UTF-8 encoded suggestions, or %null
922  */
923 ENCHANT_MODULE_EXPORT (char **)
924 enchant_dict_suggest (EnchantDict * dict, const char *const word,
925                           ssize_t len, size_t * out_n_suggs)
926 {
927         EnchantSession * session;
928         size_t n_suggs = 0, n_dict_suggs = 0, n_pwl_suggs = 0, n_suggsT = 0;
929         char **suggs, **dict_suggs = NULL, **pwl_suggs = NULL, **suggsT;
930
931         g_return_val_if_fail (dict, NULL);
932         g_return_val_if_fail (word, NULL);
933
934         if (len < 0)
935                 len = strlen (word);
936
937         g_return_val_if_fail (len, NULL);
938         g_return_val_if_fail (g_utf8_validate(word, len, NULL), NULL);
939
940         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
941         enchant_session_clear_error (session);
942         /* Check for suggestions from provider dictionary */
943         if (dict->suggest) 
944                 {
945                         dict_suggs = (*dict->suggest) (dict, word, len, 
946                                                         &n_dict_suggs);
947                         if(dict_suggs)
948                                 {
949                                         suggsT = enchant_dict_get_good_suggestions(dict, dict_suggs, n_dict_suggs, &n_suggsT);
950                                         enchant_provider_free_string_list (session->provider, dict_suggs);
951                                         dict_suggs = suggsT;
952                                         n_dict_suggs = n_suggsT;
953                                 }
954                 }
955
956         /* Check for suggestions from personal dictionary */
957         if(session->personal)
958                 {
959                         pwl_suggs = enchant_pwl_suggest(session->personal, word, len, dict_suggs, &n_pwl_suggs);
960                         if(pwl_suggs)
961                                 {
962                                         suggsT = enchant_dict_get_good_suggestions(dict, pwl_suggs, n_pwl_suggs, &n_suggsT);
963                                         enchant_pwl_free_string_list (session->personal, pwl_suggs);
964                                         pwl_suggs = suggsT;
965                                         n_pwl_suggs = n_suggsT;
966                                 }
967                 }       
968         /* Clone suggestions if there are any */
969         n_suggs = n_pwl_suggs + n_dict_suggs;
970         if (n_suggs > 0)
971                 {
972                         suggs = g_new0 (char *, n_suggs + 1);
973
974                         /* Copy over suggestions from dict, if no dupes */
975                         n_suggs = enchant_dict_merge_suggestions(dict, 
976                                                                  suggs, 0, 
977                                                                  dict_suggs, n_dict_suggs);
978
979                         /* Copy over suggestions from pwl, if no dupes */
980                         n_suggs = enchant_dict_merge_suggestions(dict, 
981                                                                  suggs, n_suggs, 
982                                                                  pwl_suggs, n_pwl_suggs);
983                         if(n_suggs == 0)
984                         {
985                                 g_free(suggs);
986                                 suggs = NULL;
987                         }
988                 }
989         else 
990                 {
991                         suggs = NULL;
992                 }
993         
994         g_strfreev(dict_suggs);
995         g_strfreev(pwl_suggs);
996
997         if (out_n_suggs)
998                 *out_n_suggs = n_suggs;
999
1000         return suggs;
1001 }
1002
1003 /**
1004  * enchant_dict_add
1005  * @dict: A non-null #EnchantDict
1006  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1007  * @len: The byte length of @word, or -1 for strlen (@word)
1008  *
1009  * Remarks: if the word exists in the exclude dictionary, it will be removed from the 
1010  *          exclude dictionary
1011  */
1012 ENCHANT_MODULE_EXPORT (void)
1013 enchant_dict_add (EnchantDict * dict, const char *const word,
1014                          ssize_t len)
1015 {
1016         EnchantSession * session;
1017
1018         g_return_if_fail (dict);
1019         g_return_if_fail (word);
1020
1021         if (len < 0)
1022                 len = strlen (word);
1023
1024         g_return_if_fail (len);
1025         g_return_if_fail (g_utf8_validate(word, len, NULL));
1026
1027         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1028         enchant_session_clear_error (session);
1029         enchant_session_add_personal (session, word, len);
1030         enchant_session_remove_exclude (session, word, len);
1031         
1032         if (dict->add_to_personal)
1033                 (*dict->add_to_personal) (dict, word, len);
1034 }
1035
1036 /**
1037  * enchant_dict_add_to_pwl
1038  * @dict: A non-null #EnchantDict
1039  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1040  * @len: The byte length of @word, or -1 for strlen (@word)
1041  *
1042  * DEPRECATED. Please use enchant_dict_add() instead.
1043  */
1044 ENCHANT_MODULE_EXPORT (void)
1045 enchant_dict_add_to_pwl (EnchantDict * dict, const char *const word,
1046                          ssize_t len)
1047 {
1048         enchant_dict_add(dict,word,len);
1049 }
1050
1051 /**
1052  * enchant_dict_add_to_personal
1053  * @dict: A non-null #EnchantDict
1054  * @word: The non-null word you wish to add to your personal dictionary, in UTF-8 encoding
1055  * @len: The byte length of @word, or -1 for strlen (@word)
1056  *
1057  * DEPRECATED. Please use enchant_dict_add() instead.
1058  */
1059 ENCHANT_MODULE_EXPORT (void)
1060 enchant_dict_add_to_personal (EnchantDict * dict, const char *const word,
1061                                   ssize_t len)
1062 {
1063         enchant_dict_add(dict, word, len);
1064 }
1065
1066 /**
1067  * enchant_dict_add_to_session
1068  * @dict: A non-null #EnchantDict
1069  * @word: The non-null word you wish to add to this spell-checking session, in UTF-8 encoding
1070  * @len: The byte length of @word, or -1 for strlen (@word)
1071  *
1072  */
1073 ENCHANT_MODULE_EXPORT (void)
1074 enchant_dict_add_to_session (EnchantDict * dict, const char *const word,
1075                                  ssize_t len)
1076 {
1077         EnchantSession * session;
1078
1079         g_return_if_fail (dict);
1080         g_return_if_fail (word);
1081
1082         if (len < 0)
1083                 len = strlen (word);
1084         
1085         g_return_if_fail (len);
1086         g_return_if_fail (g_utf8_validate(word, len, NULL));
1087
1088         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1089         enchant_session_clear_error (session);
1090
1091         enchant_session_add (session, word, len);
1092         if (dict->add_to_session)
1093                 (*dict->add_to_session) (dict, word, len);
1094 }
1095
1096 /**
1097  * enchant_dict_is_added
1098  * @dict: A non-null #EnchantDict
1099  * @word: The word you wish to see if it has been added (to your session or dict) in UTF8 encoding
1100  * @len: the byte length of @word, or -1 for strlen (@word)
1101  */
1102 ENCHANT_MODULE_EXPORT (int)
1103 enchant_dict_is_added (EnchantDict * dict, const char *const word,
1104                                 ssize_t len)
1105 {
1106         EnchantSession * session;
1107
1108         g_return_val_if_fail (dict, 0);
1109         g_return_val_if_fail (word, 0);
1110
1111         if (len < 0)
1112                 len = strlen (word);
1113         
1114         g_return_val_if_fail (len, 0);
1115         g_return_val_if_fail (g_utf8_validate(word, len, NULL), 0);
1116
1117         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1118         enchant_session_clear_error (session);
1119
1120         return enchant_session_contains (session, word, len);
1121 }
1122
1123 /**
1124  * enchant_dict_is_in_session
1125  * @dict: A non-null #EnchantDict
1126  * @word: The word you wish to see if it's in your session in UTF8 encoding
1127  * @len: the byte length of @word, or -1 for strlen (@word)
1128  *
1129  * DEPRECATED. Please use enchant_dict_is_added() instead.
1130 */
1131 ENCHANT_MODULE_EXPORT (int)
1132 enchant_dict_is_in_session (EnchantDict * dict, const char *const word,
1133                                 ssize_t len)
1134 {
1135         return enchant_dict_is_added(dict, word, len);
1136 }
1137
1138 /**
1139  * enchant_dict_remove
1140  * @dict: A non-null #EnchantDict
1141  * @word: The non-null word you wish to add to your exclude dictionary and 
1142  *        remove from the personal dictionary, in UTF-8 encoding
1143  * @len: The byte length of @word, or -1 for strlen (@word)
1144  *
1145  */
1146 ENCHANT_MODULE_EXPORT (void)
1147 enchant_dict_remove (EnchantDict * dict, const char *const word,
1148                          ssize_t len)
1149 {
1150         EnchantSession * session;
1151
1152         g_return_if_fail (dict);
1153         g_return_if_fail (word);
1154
1155         if (len < 0)
1156                 len = strlen (word);
1157
1158         g_return_if_fail (len);
1159         g_return_if_fail (g_utf8_validate(word, len, NULL));
1160
1161         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1162         enchant_session_clear_error (session);
1163
1164         enchant_session_remove_personal (session, word, len);
1165         enchant_session_add_exclude(session, word, len);
1166         
1167         if (dict->add_to_exclude)
1168                 (*dict->add_to_exclude) (dict, word, len);
1169 }
1170
1171 /**
1172  * enchant_dict_remove_from_session
1173  * @dict: A non-null #EnchantDict
1174  * @word: The non-null word you wish to exclude from this spell-checking session, in UTF-8 encoding
1175  * @len: The byte length of @word, or -1 for strlen (@word)
1176  *
1177  */
1178 ENCHANT_MODULE_EXPORT (void)
1179 enchant_dict_remove_from_session (EnchantDict * dict, const char *const word,
1180                          ssize_t len)
1181 {
1182         EnchantSession * session;
1183
1184         g_return_if_fail (dict);
1185         g_return_if_fail (word);
1186
1187         if (len < 0)
1188                 len = strlen (word);
1189         
1190         g_return_if_fail (len);
1191         g_return_if_fail (g_utf8_validate(word, len, NULL));
1192
1193         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1194         enchant_session_clear_error (session);
1195
1196         enchant_session_remove (session, word, len);
1197 }
1198
1199 /**
1200  * enchant_dict_is_removed
1201  * @dict: A non-null #EnchantDict
1202  * @word: The word you wish to see if it has been removed (from your session or dict) in UTF8 encoding
1203  * @len: the byte length of @word, or -1 for strlen (@word)
1204  */
1205 ENCHANT_MODULE_EXPORT (int)
1206 enchant_dict_is_removed (EnchantDict * dict, const char *const word,
1207                                 ssize_t len)
1208 {
1209         EnchantSession * session;
1210
1211         g_return_val_if_fail (dict, 0);
1212         g_return_val_if_fail (word, 0);
1213
1214         if (len < 0)
1215                 len = strlen (word);
1216         
1217         g_return_val_if_fail (len, 0);
1218         g_return_val_if_fail (g_utf8_validate(word, len, NULL), 0);
1219
1220         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1221         enchant_session_clear_error (session);
1222
1223         return enchant_session_exclude (session, word, len);
1224 }
1225
1226 /**
1227  * enchant_dict_store_replacement
1228  * @dict: A non-null #EnchantDict
1229  * @mis: The non-null word you wish to add a correction for, in UTF-8 encoding
1230  * @mis_len: The byte length of @mis, or -1 for strlen (@mis)
1231  * @cor: The non-null correction word, in UTF-8 encoding
1232  * @cor_len: The byte length of @cor, or -1 for strlen (@cor)
1233  *
1234  * Notes that you replaced @mis with @cor, so it's possibly more likely
1235  * that future occurrences of @mis will be replaced with @cor. So it might
1236  * bump @cor up in the suggestion list.
1237  */
1238 ENCHANT_MODULE_EXPORT (void)
1239 enchant_dict_store_replacement (EnchantDict * dict,
1240                                 const char *const mis, ssize_t mis_len,
1241                                 const char *const cor, ssize_t cor_len)
1242 {
1243         EnchantSession * session;
1244
1245         g_return_if_fail (dict);
1246         g_return_if_fail (mis);
1247         g_return_if_fail (cor);
1248
1249         if (mis_len < 0)
1250                 mis_len = strlen (mis);
1251
1252         if (cor_len < 0)
1253                 cor_len = strlen (cor);
1254
1255         g_return_if_fail (mis_len);
1256         g_return_if_fail (cor_len);
1257
1258         g_return_if_fail (g_utf8_validate(mis, mis_len, NULL));
1259         g_return_if_fail (g_utf8_validate(cor, cor_len, NULL));
1260
1261         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1262         enchant_session_clear_error (session);
1263         
1264         /* if it's not implemented, it's not worth emulating */
1265         if (dict->store_replacement)
1266                 (*dict->store_replacement) (dict, mis, mis_len, cor, cor_len);
1267 }
1268
1269 /**
1270  * enchant_dict_free_string_list
1271  * @dict: A non-null #EnchantDict
1272  * @string_list: A non-null string list returned from enchant_dict_suggest
1273  *
1274  * Releases the string list
1275  */
1276 ENCHANT_MODULE_EXPORT (void)
1277 enchant_dict_free_string_list (EnchantDict * dict, char **string_list)
1278 {
1279         EnchantSession * session;
1280
1281         g_return_if_fail (dict);
1282         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1283         enchant_session_clear_error (session);
1284         g_strfreev(string_list);
1285 }
1286
1287 /**
1288  * enchant_dict_free_suggestions
1289  * @dict: A non-null #EnchantDict
1290  * @suggestions: The non-null suggestion list returned by
1291  *               'enchant_dict_suggest'
1292  *
1293  * Releases the suggestions
1294  * This function is DEPRECATED. Please use enchant_dict_free_string_list() instead.
1295  */
1296 ENCHANT_MODULE_EXPORT (void)
1297 enchant_dict_free_suggestions (EnchantDict * dict, char **suggestions)
1298 {
1299         enchant_dict_free_string_list (dict, suggestions);
1300 }
1301
1302 /**
1303  * enchant_dict_describe
1304  * @broker: A non-null #EnchantDict
1305  * @dict: A non-null #EnchantDictDescribeFn
1306  * @user_data: Optional user-data
1307  *
1308  * Describes an individual dictionary
1309  */
1310 ENCHANT_MODULE_EXPORT (void)
1311 enchant_dict_describe (EnchantDict * dict,
1312                            EnchantDictDescribeFn fn,
1313                            void * user_data)
1314 {
1315         EnchantSession * session;
1316         EnchantProvider * provider;
1317         GModule *module;
1318
1319         const char * tag, * name, * desc, * file;
1320
1321         g_return_if_fail (dict);
1322         g_return_if_fail (fn);
1323
1324         session = ((EnchantDictPrivateData*)dict->enchant_private_data)->session;
1325         enchant_session_clear_error (session);
1326         provider = session->provider;
1327
1328         if (provider) 
1329                 {
1330                         module = (GModule *) provider->enchant_private_data;
1331                         file = g_module_name (module);  
1332                         name = (*provider->identify) (provider);
1333                         desc = (*provider->describe) (provider);
1334                 } 
1335         else 
1336                 {
1337                         file = session->personal_filename;
1338                         name = "Personal Wordlist";
1339                         desc = "Personal Wordlist";
1340                 }
1341         
1342         tag = session->language_tag;
1343         (*fn) (tag, name, desc, file, user_data);
1344 }
1345
1346 /***********************************************************************************/
1347 /***********************************************************************************/
1348
1349 static void
1350 enchant_broker_clear_error (EnchantBroker * broker)
1351 {
1352         if (broker->error) 
1353                 {
1354                         g_free (broker->error);
1355                         broker->error = NULL;
1356                 }
1357 }
1358
1359 static void
1360 enchant_broker_set_error (EnchantBroker * broker, const char * const err)
1361 {
1362         enchant_broker_clear_error (broker);
1363         broker->error = g_strdup (err); 
1364 }
1365
1366 static int
1367 enchant_provider_is_valid(EnchantProvider * provider)
1368 {
1369         if(provider == NULL)
1370                 {
1371                         g_warning ("EnchantProvider cannot be NULL\n");
1372                         return 0;
1373                 }
1374
1375         if(provider->identify == NULL)
1376                 {
1377                         g_warning ("EnchantProvider's identify method cannot be NULL\n");
1378                         return 0;
1379                 }
1380         else if(!g_utf8_validate((*provider->identify)(provider), -1, NULL))
1381                 {
1382                         g_warning ("EnchantProvider's identify method does not return valid utf8.\n");
1383                         return 0;
1384                 }
1385
1386         if(provider->describe == NULL)
1387                 {
1388                         g_warning ("EnchantProvider's describe method cannot be NULL\n");
1389                         return 0;
1390                 }
1391         else if(!g_utf8_validate((*provider->describe)(provider), -1, NULL))
1392                 {
1393                         g_warning ("EnchantProvider's describe method does not return valid utf8.\n");
1394                         return 0;
1395                 }
1396
1397         return 1;
1398 }
1399
1400 static void
1401 enchant_load_providers_in_dir (EnchantBroker * broker, const char *dir_name)
1402 {
1403         GModule *module = NULL;
1404         GDir *dir;
1405         G_CONST_RETURN char *dir_entry;
1406         size_t entry_len, g_module_suffix_len;
1407         
1408         char * filename;
1409         
1410         EnchantProvider *provider;
1411         EnchantProviderInitFunc init_func;
1412         EnchantPreConfigureFunc conf_func;
1413         
1414         dir = g_dir_open (dir_name, 0, NULL);
1415         if (!dir) 
1416                 return;
1417         
1418         g_module_suffix_len = strlen (G_MODULE_SUFFIX);
1419
1420         while ((dir_entry = g_dir_read_name (dir)) != NULL)
1421                 {
1422                         provider = 0;
1423
1424                         entry_len = strlen (dir_entry);
1425                         if ((entry_len > g_module_suffix_len) && 
1426                                 !strcmp(dir_entry+(entry_len-g_module_suffix_len), G_MODULE_SUFFIX))
1427                                 {
1428 #ifdef _WIN32
1429                                         /* Suppress error popups for failing to load plugins */
1430                                         UINT old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS);
1431 #endif
1432                                         filename = g_build_filename (dir_name, dir_entry, NULL);
1433                                         
1434                                         module = g_module_open (filename, (GModuleFlags) 0);
1435                                         if (module) 
1436                                                 {
1437                                                         if (g_module_symbol
1438                                                                 (module, "init_enchant_provider", (gpointer *) (&init_func))
1439                                                                 && init_func)
1440                                                                 {
1441                                                                         provider = init_func ();
1442                                                                         if (!enchant_provider_is_valid(provider))
1443                                                                                 {
1444                                                                                         g_warning ("Error loading plugin: %s's init_enchant_provider returned invalid provider.\n", dir_entry);
1445                                                                                         if(provider)
1446                                                                                                 {
1447                                                                                                         if(provider->dispose)
1448                                                                                                                 provider->dispose(provider);
1449
1450                                                                                                         provider = NULL;
1451                                                                                                 }
1452                                                                                         g_module_close (module);
1453                                                                                 }
1454                                                                 }
1455                                                         else
1456                                                                 {
1457                                                                         g_module_close (module);
1458                                                                 }
1459                                                 } 
1460                                         else 
1461                                                 {
1462                                                         g_warning ("Error loading plugin: %s\n", g_module_error());
1463                                                 }
1464                                         
1465                                         g_free (filename);
1466 #ifdef _WIN32
1467                                         /* Restore the original error mode */
1468                                         SetErrorMode(old_error_mode);
1469 #endif
1470                                 }
1471                         if (provider)
1472                                 {
1473                                         /* optional entry point to allow modules to look for associated files
1474                                          */
1475                                         if (g_module_symbol
1476                                                 (module, "configure_enchant_provider", (gpointer *) (&conf_func))
1477                                                 && conf_func)
1478                                                 {
1479                                                         conf_func (provider, dir_name);
1480                                                         if (!enchant_provider_is_valid(provider))
1481                                                                 {
1482                                                                         g_warning ("Error loading plugin: %s's configure_enchant_provider modified provider and it is now invalid.\n", dir_entry);
1483                                                                         if(provider->dispose)
1484                                                                                 provider->dispose(provider);
1485
1486                                                                         provider = NULL;
1487                                                                         g_module_close (module);
1488                                                                 }
1489                                                 }
1490                                 }
1491                         if (provider)
1492                                 {
1493                                         provider->enchant_private_data = (void *) module;
1494                                         provider->owner = broker;
1495                                         broker->provider_list = g_slist_append (broker->provider_list, (gpointer)provider);
1496                                 }
1497                 }
1498         
1499         g_dir_close (dir);
1500 }
1501
1502 static void
1503 enchant_load_providers (EnchantBroker * broker)
1504 {
1505         GSList *module_dirs, *iter;
1506
1507         module_dirs = enchant_get_module_dirs();
1508         
1509         for (iter = module_dirs; iter; iter = iter->next)
1510                 {
1511                         enchant_load_providers_in_dir (broker, iter->data);
1512                 }
1513
1514         g_slist_foreach (module_dirs, (GFunc)g_free, NULL);
1515         g_slist_free (module_dirs);
1516 }
1517
1518 static void
1519 enchant_load_ordering_from_file (EnchantBroker * broker, const char * file)
1520 {
1521         char line [1024];
1522         char * tag, * ordering;
1523
1524         size_t i, len;
1525
1526         FILE * f;
1527
1528         f = enchant_fopen (file, "r");
1529         if (!f)
1530                 return;
1531
1532         while (NULL != fgets (line, sizeof(line), f)) {
1533                 for (i = 0, len = strlen(line); i < len && line[i] != ':'; i++) 
1534                         ;
1535
1536                 if (i < len) 
1537                         {
1538                                 tag = g_strndup (line, i);
1539                                 ordering = g_strndup (line+(i+1), len - i);                     
1540                                 
1541                                 enchant_broker_set_ordering (broker, tag, ordering);
1542                                 
1543                                 g_free (tag);
1544                                 g_free (ordering);
1545                         }
1546         }
1547         
1548         fclose (f);
1549 }
1550
1551 static void
1552 enchant_load_provider_ordering (EnchantBroker * broker)
1553 {
1554         GSList *conf_dirs, *iter;
1555
1556         broker->provider_ordering = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1557
1558         /* we want the user's dirs to show up last, so they override system dirs */
1559         conf_dirs = g_slist_reverse (enchant_get_conf_dirs ());
1560         for (iter = conf_dirs; iter; iter = iter->next)
1561                 {
1562                         char *ordering_file;
1563                         ordering_file = g_build_filename (iter->data, "enchant.ordering", NULL);
1564                         enchant_load_ordering_from_file (broker, ordering_file);
1565                         g_free (ordering_file); 
1566                 }
1567
1568         g_slist_foreach (conf_dirs, (GFunc)g_free, NULL);
1569         g_slist_free (conf_dirs);
1570 }
1571
1572 static GSList *
1573 enchant_get_ordered_providers (EnchantBroker * broker,
1574                                    const char * const tag)
1575 {
1576         EnchantProvider *provider;
1577         GSList * list = NULL, * iter = NULL;
1578
1579         char * ordering = NULL, ** tokens, *token;
1580         size_t i;
1581
1582         ordering = (char *)g_hash_table_lookup (broker->provider_ordering, (gpointer)tag);
1583         if (!ordering)
1584                 ordering = (char *)g_hash_table_lookup (broker->provider_ordering, (gpointer)"*");
1585
1586         if (!ordering) 
1587                 {
1588                         /* return an unordered copy of the list */
1589                         for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter))
1590                                         list = g_slist_append (list, iter->data);
1591                         return list;
1592                 }
1593         
1594         tokens = g_strsplit (ordering, ",", 0);
1595         if (tokens) 
1596                 {
1597                         for (i = 0; tokens[i]; i++) 
1598                                 {
1599                                         token = g_strstrip(tokens[i]);
1600                                         
1601                                         for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter)) 
1602                                                 {
1603                                                         provider = (EnchantProvider*)iter->data;
1604                                                         
1605                                                         if (provider && !strcmp (token, (*provider->identify)(provider)))
1606                                                                 list = g_slist_append (list, (gpointer)provider);
1607                                                 }
1608                                 }
1609                         
1610                         g_strfreev (tokens);
1611                 }
1612         
1613         /* providers not in the list need to be appended at the end */
1614         for (iter = broker->provider_list; iter != NULL; iter = g_slist_next (iter)) 
1615                 {
1616                         if (!g_slist_find (list, iter->data))
1617                                 list = g_slist_append (list, iter->data);
1618                 }
1619
1620         return list;
1621 }
1622
1623 static void
1624 enchant_dict_destroyed (gpointer data)
1625 {
1626         EnchantDict *dict;
1627         EnchantProvider *owner;
1628         EnchantSession *session;
1629         EnchantDictPrivateData *enchant_dict_private_data;
1630         
1631         g_return_if_fail (data);
1632         
1633         dict = (EnchantDict *) data;
1634         enchant_dict_private_data = (EnchantDictPrivateData*)dict->enchant_private_data;
1635         session = enchant_dict_private_data->session;
1636         owner = session->provider;
1637         
1638         if (owner && owner->dispose_dict) 
1639                 (*owner->dispose_dict) (owner, dict);
1640         else if(session->is_pwl)
1641                 g_free (dict);
1642
1643         g_free(enchant_dict_private_data);
1644
1645         enchant_session_destroy (session);
1646 }
1647
1648 static void
1649 enchant_provider_free (gpointer data, gpointer user_data)
1650 {
1651         EnchantProvider *provider;
1652         GModule *module;
1653         
1654         g_return_if_fail (data);
1655         
1656         provider = (EnchantProvider *) data;    
1657         module = (GModule *) provider->enchant_private_data;
1658         
1659         if (provider->dispose) 
1660                 (*provider->dispose) (provider);
1661         
1662         /* close module only after invoking dispose */
1663         g_module_close (module);
1664 }
1665
1666 /**
1667  * enchant_broker_init
1668  *
1669  * Returns: A new broker object capable of requesting
1670  * dictionaries from
1671  */
1672 ENCHANT_MODULE_EXPORT (EnchantBroker *) 
1673 enchant_broker_init (void)
1674 {
1675         EnchantBroker *broker = NULL;
1676         
1677         g_return_val_if_fail (g_module_supported (), NULL);
1678
1679 #ifdef ENABLE_BINRELOC
1680         {
1681                 static gboolean binreloc_initialized = FALSE;
1682
1683                 if (!binreloc_initialized)
1684                         {
1685                                 (void)gbr_init_lib (NULL);
1686                                 binreloc_initialized = TRUE;
1687                         }
1688         }
1689 #endif
1690         
1691         broker = g_new0 (EnchantBroker, 1);
1692         
1693         broker->dict_map = g_hash_table_new_full (g_str_hash, g_str_equal,
1694                                                   g_free, enchant_dict_destroyed);
1695         broker->params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1696         enchant_load_providers (broker);        
1697         enchant_load_provider_ordering (broker);
1698
1699         return broker;
1700 }
1701
1702 /**
1703  * enchant_broker_free
1704  * @broker: A non-null #EnchantBroker
1705  *
1706  * Destroys the broker object. Must only be called once per broker init
1707  */
1708 ENCHANT_MODULE_EXPORT (void) 
1709 enchant_broker_free (EnchantBroker * broker)
1710 {
1711         guint n_remaining;
1712
1713         g_return_if_fail (broker);       
1714
1715         n_remaining = g_hash_table_size (broker->dict_map);
1716         if (n_remaining) 
1717                 {
1718                         g_warning ("%u dictionaries weren't free'd.\n", n_remaining);
1719                 }
1720
1721         /* will destroy any remaining dictionaries for us */
1722         g_hash_table_destroy (broker->dict_map);
1723         g_hash_table_destroy (broker->provider_ordering);
1724         g_hash_table_destroy (broker->params);
1725         
1726         g_slist_foreach (broker->provider_list, enchant_provider_free, NULL);
1727         g_slist_free (broker->provider_list);
1728
1729         enchant_broker_clear_error (broker);
1730
1731         g_free (broker);
1732 }
1733
1734 /**
1735  * enchant_broker_request_pwl_dict
1736  *
1737  * PWL is a personal wordlist file, 1 entry per line
1738  *
1739  * @pwl: A non-null pathname in the GLib file name encoding (UTF-8 on Windows)
1740  *       to the personal wordlist file
1741  *
1742  * Returns: An EnchantDict. This dictionary is reference counted.
1743  */
1744 ENCHANT_MODULE_EXPORT (EnchantDict *)
1745 enchant_broker_request_pwl_dict (EnchantBroker * broker, const char *const pwl)
1746 {
1747         EnchantSession *session;
1748         EnchantDictPrivateData *enchant_dict_private_data;
1749         EnchantDict *dict = NULL;
1750
1751         g_return_val_if_fail (broker, NULL);
1752         g_return_val_if_fail (pwl && strlen(pwl), NULL);
1753
1754         enchant_broker_clear_error (broker);
1755
1756         dict = (EnchantDict*)g_hash_table_lookup (broker->dict_map, (gpointer) pwl);
1757         if (dict) {
1758                 ((EnchantDictPrivateData*)dict->enchant_private_data)->reference_count++;
1759                 return dict;
1760         }
1761
1762         /* since the broker pwl file is a read/write file (there is no readonly dictionary associated)
1763          * there is no need for complementary exclude file to add a word to. The word just needs to be
1764          * removed from the broker pwl file
1765          */
1766         session = enchant_session_new_with_pwl (NULL, pwl, NULL, "Personal Wordlist", TRUE);
1767         if (!session) 
1768                 {
1769                         broker->error = g_strdup_printf ("Couldn't open personal wordlist '%s'", pwl);
1770                         return NULL;
1771                 }
1772
1773         session->is_pwl = 1;
1774
1775         dict = g_new0 (EnchantDict, 1);
1776         enchant_dict_private_data = g_new0 (EnchantDictPrivateData, 1);
1777         enchant_dict_private_data->reference_count = 1;
1778         enchant_dict_private_data->session = session;
1779         dict->enchant_private_data = (void *)enchant_dict_private_data;
1780         
1781
1782         g_hash_table_insert (broker->dict_map, (gpointer)g_strdup (pwl), dict);
1783
1784         return dict;
1785 }
1786
1787 static EnchantDict *
1788 _enchant_broker_request_dict (EnchantBroker * broker, const char *const tag)
1789 {
1790         EnchantDict * dict;
1791         GSList * list;
1792         GSList * listIter;
1793
1794         dict = (EnchantDict*)g_hash_table_lookup (broker->dict_map, (gpointer) tag);
1795         if (dict) {
1796                 ((EnchantDictPrivateData*)dict->enchant_private_data)->reference_count++;
1797                 return dict;
1798         }
1799
1800         list = enchant_get_ordered_providers (broker, tag);
1801         for (listIter = list; listIter != NULL; listIter = g_slist_next (listIter))
1802                 {
1803                         EnchantProvider * provider;
1804
1805                         provider = (EnchantProvider *) listIter->data;
1806                         
1807                         if (provider->request_dict)
1808                                 {
1809                                         dict = (*provider->request_dict) (provider, tag);
1810                                         
1811                                         if (dict)
1812                                                 {
1813                                                         EnchantSession *session;
1814                                                         EnchantDictPrivateData *enchant_dict_private_data;
1815         
1816                                                         session = enchant_session_new (provider, tag);
1817                                                         enchant_dict_private_data = g_new0 (EnchantDictPrivateData, 1);
1818                                                         enchant_dict_private_data->reference_count = 1;
1819                                                         enchant_dict_private_data->session = session;
1820                                                         dict->enchant_private_data = (void *)enchant_dict_private_data;
1821                                                         g_hash_table_insert (broker->dict_map, (gpointer)g_strdup (tag), dict);
1822                                                         break;
1823                                                 }
1824                                 }
1825                 }
1826
1827         g_slist_free (list);
1828
1829         return dict;
1830 }
1831
1832 /**
1833  * enchant_broker_request_dict
1834  * @broker: A non-null #EnchantBroker
1835  * @tag: The non-null language tag you wish to request a dictionary for ("en_US", "de_DE", ...)
1836  *
1837  * Returns: An #EnchantDict, or %null if no suitable dictionary could be found. This dictionary is reference counted.
1838  */
1839 ENCHANT_MODULE_EXPORT (EnchantDict *)
1840 enchant_broker_request_dict (EnchantBroker * broker, const char *const tag)
1841 {
1842         EnchantDict *dict = NULL;
1843         char * normalized_tag;
1844
1845         g_return_val_if_fail (broker, NULL);
1846         g_return_val_if_fail (tag && strlen(tag), NULL);
1847
1848         enchant_broker_clear_error (broker);
1849         
1850         normalized_tag = enchant_normalize_dictionary_tag (tag);
1851         if(!enchant_is_valid_dictionary_tag(normalized_tag))
1852                 {
1853                         enchant_broker_set_error (broker, "invalid tag character found");
1854                 }
1855         else if ((dict = _enchant_broker_request_dict (broker, normalized_tag)) == NULL)
1856                 {
1857                         char * iso_639_only_tag;
1858
1859                         iso_639_only_tag = enchant_iso_639_from_tag (normalized_tag);
1860
1861                         dict = _enchant_broker_request_dict (broker, iso_639_only_tag);
1862
1863                         g_free (iso_639_only_tag);
1864                 }
1865
1866         g_free (normalized_tag);
1867         
1868         return dict;
1869 }
1870
1871 /**
1872  * enchant_broker_describe
1873  * @broker: A non-null #EnchantBroker
1874  * @fn: A non-null #EnchantBrokerDescribeFn
1875  * @user_data: Optional user-data
1876  *
1877  * Enumerates the Enchant providers and tells
1878  * you some rudimentary information about them.
1879  */
1880 ENCHANT_MODULE_EXPORT (void)
1881 enchant_broker_describe (EnchantBroker * broker,
1882                          EnchantBrokerDescribeFn fn,
1883                          void * user_data)
1884 {
1885         GSList *list;
1886         EnchantProvider *provider;
1887         GModule *module;
1888
1889         const char * name, * desc, * file;
1890
1891         g_return_if_fail (broker);
1892         g_return_if_fail (fn);
1893
1894         enchant_broker_clear_error (broker);
1895
1896         for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
1897                 {
1898                         provider = (EnchantProvider *) list->data;
1899                         module = (GModule *) provider->enchant_private_data;
1900                         
1901                         name = (*provider->identify) (provider);
1902                         desc = (*provider->describe) (provider);
1903                         file = g_module_name (module);
1904                         
1905                         (*fn) (name, desc, file, user_data);
1906                 }
1907 }
1908
1909 /**
1910  * enchant_broker_list_dicts
1911  * @broker: A non-null #EnchantBroker
1912  * @fn: A non-null #EnchantDictDescribeFn
1913  * @user_data: Optional user-data
1914  *
1915  * Enumerates the dictionaries available from
1916  * all Enchant providers.
1917  */
1918 ENCHANT_MODULE_EXPORT (void)
1919 enchant_broker_list_dicts (EnchantBroker * broker,
1920                            EnchantDictDescribeFn fn,
1921                            void * user_data)
1922 {
1923         GSList *list;
1924         GHashTable *tags;
1925         
1926         g_return_if_fail (broker);
1927         g_return_if_fail (fn);
1928
1929         tags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1930
1931         enchant_broker_clear_error (broker);
1932
1933         for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
1934                 {
1935                         EnchantProvider *provider;
1936                         GModule *module;
1937
1938                         provider = (EnchantProvider *) list->data;
1939                         module = (GModule *) provider->enchant_private_data;
1940
1941                         if (provider->list_dicts)
1942                                 {
1943                                         const char * tag, * name, * desc, * file;
1944                                         size_t n_dicts, i;
1945                                         char ** dicts;                                 
1946
1947                                         dicts = (*provider->list_dicts) (provider, &n_dicts);
1948                                         name = (*provider->identify) (provider);
1949                                         desc = (*provider->describe) (provider);
1950                                         file = g_module_name (module);
1951
1952                                         for (i = 0; i < n_dicts; i++)
1953                                                 {
1954                                                         tag = dicts[i];
1955                                                         if(enchant_is_valid_dictionary_tag(tag) &&
1956                                                            !g_hash_table_lookup (tags, tag))
1957                                                                 {
1958                                                                         g_hash_table_insert (tags, g_strdup (tag), GINT_TO_POINTER(TRUE));
1959                                                                         (*fn) (tag, name, desc, file, user_data);
1960                                                                 }
1961                                                 }
1962
1963                                         enchant_provider_free_string_list (provider, dicts);
1964                                 }       
1965                 }
1966
1967         g_hash_table_destroy (tags);
1968 }
1969
1970 /**
1971  * enchant_broker_free_dict
1972  * @broker: A non-null #EnchantBroker
1973  * @dict: A non-null #EnchantDict
1974  *
1975  * Releases the dictionary when you are done using it. Must only be called once per dictionary request
1976  */
1977 ENCHANT_MODULE_EXPORT (void)
1978 enchant_broker_free_dict (EnchantBroker * broker, EnchantDict * dict)
1979 {
1980         EnchantSession * session;
1981         EnchantDictPrivateData * dict_private_data;
1982
1983         g_return_if_fail (broker);
1984         g_return_if_fail (dict);
1985
1986         enchant_broker_clear_error (broker);
1987
1988         dict_private_data = (EnchantDictPrivateData*)dict->enchant_private_data;
1989         dict_private_data->reference_count--;
1990         if(dict_private_data->reference_count == 0)
1991                 {
1992                         session = dict_private_data->session;
1993         
1994                         if (session->provider)
1995                                 g_hash_table_remove (broker->dict_map, session->language_tag);
1996                         else
1997                                 g_hash_table_remove (broker->dict_map, session->personal_filename);
1998                 }
1999 }
2000
2001 static int
2002 _enchant_provider_dictionary_exists (EnchantProvider * provider,
2003                                          const char * const tag)
2004 {
2005         int exists = 0;
2006
2007         if (provider->dictionary_exists)
2008                 {
2009                         exists = (*provider->dictionary_exists) (provider, tag);
2010                 }
2011         else if (provider->list_dicts)
2012                 {
2013                         size_t n_dicts, i;
2014                         char ** dicts;                                 
2015                         
2016                         dicts = (*provider->list_dicts) (provider, &n_dicts);
2017                         
2018                         for (i = 0; (i < n_dicts) && !exists; i++)
2019                                 {
2020                                         if (!strcmp(dicts[i], tag)) 
2021                                                 exists = 1;
2022                                 }
2023                         
2024                         enchant_provider_free_string_list (provider, dicts);
2025                 }
2026         else if (provider->request_dict)
2027                 {
2028                         EnchantDict *dict;
2029
2030                         dict = (*provider->request_dict) (provider, tag);
2031                         if (dict)
2032                                 {
2033                                         if (provider->dispose_dict) 
2034                                                 (*provider->dispose_dict) (provider, dict);
2035                                         exists = 1;
2036                                 }
2037                 }
2038
2039         return exists;
2040 }
2041
2042 static int
2043 _enchant_broker_dict_exists (EnchantBroker * broker,
2044                                  const char * const tag)
2045 {
2046         GSList * list;
2047
2048         /* don't query the providers if it is an empty string */
2049         if (tag == NULL || *tag == '\0') {
2050                 return 0;
2051         }
2052
2053         /* don't query the providers if we can just do a quick map lookup */
2054         if (g_hash_table_lookup (broker->dict_map, (gpointer) tag) != NULL) {
2055                 return 1;
2056         }
2057
2058         for (list = broker->provider_list; list != NULL; list = g_slist_next (list))
2059                 {
2060                         EnchantProvider * provider;
2061
2062                         provider = (EnchantProvider *) list->data;
2063
2064                         if (_enchant_provider_dictionary_exists (provider, tag))
2065                                 {
2066                                         return 1;
2067                                 }
2068                 }
2069
2070         return 0;
2071 }
2072
2073 /**
2074  * enchant_broker_dict_exists
2075  * @broker: A non-null #EnchantBroker
2076  * @tag: The non-null language tag you wish to request a dictionary for ("en_US", "de_DE", ...)
2077  *
2078  * Return existance of the requested dictionary (1 == true, 0 == false)
2079  */
2080 ENCHANT_MODULE_EXPORT (int)
2081 enchant_broker_dict_exists (EnchantBroker * broker,
2082                                 const char * const tag)
2083 {
2084         char * normalized_tag;
2085         int exists = 0;
2086
2087         g_return_val_if_fail (broker, 0);
2088         g_return_val_if_fail (tag && strlen(tag), 0);
2089
2090         enchant_broker_clear_error (broker);
2091
2092         normalized_tag = enchant_normalize_dictionary_tag (tag);
2093
2094         if(!enchant_is_valid_dictionary_tag(normalized_tag))
2095                 {
2096                         enchant_broker_set_error (broker, "invalid tag character found");
2097                 }
2098         else if ((exists = _enchant_broker_dict_exists (broker, normalized_tag)) == 0)
2099                 {
2100                         char * iso_639_only_tag;
2101
2102                         iso_639_only_tag = enchant_iso_639_from_tag (normalized_tag);
2103
2104                         if (strcmp (normalized_tag, iso_639_only_tag) != 0)
2105                                 {
2106                                         exists = _enchant_broker_dict_exists (broker, iso_639_only_tag);
2107                                 }
2108
2109                         g_free (iso_639_only_tag);
2110                 }
2111
2112         g_free (normalized_tag);
2113         return exists;
2114 }
2115
2116 /**
2117  * enchant_broker_set_ordering
2118  * @broker: A non-null #EnchantBroker
2119  * @tag: A non-null language tag (en_US)
2120  * @ordering: A non-null ordering (aspell,myspell,ispell,uspell,hspell)
2121  *
2122  * Declares a preference of dictionaries to use for the language
2123  * described/referred to by @tag. The ordering is a comma delimited
2124  * list of provider names. As a special exception, the "*" tag can
2125  * be used as a language tag to declare a default ordering for any
2126  * language that does not explictly declare an ordering.
2127  */
2128 ENCHANT_MODULE_EXPORT (void)
2129 enchant_broker_set_ordering (EnchantBroker * broker,
2130                                  const char * const tag,
2131                                  const char * const ordering)
2132 {
2133         char * tag_dupl;
2134         char * ordering_dupl;
2135
2136         g_return_if_fail (broker);
2137         g_return_if_fail (tag && strlen(tag));
2138         g_return_if_fail (ordering && strlen(ordering));
2139
2140         enchant_broker_clear_error (broker);
2141
2142         tag_dupl = enchant_normalize_dictionary_tag (tag);
2143
2144         ordering_dupl = g_strdup (ordering);
2145         ordering_dupl = g_strstrip (ordering_dupl);
2146
2147         if (tag_dupl && strlen(tag_dupl) &&
2148                 ordering_dupl && strlen(ordering_dupl)) 
2149                 {                       
2150                         /* we will free ordering_dupl && tag_dupl when the hash is destroyed */
2151                         g_hash_table_insert (broker->provider_ordering, (gpointer)tag_dupl,
2152                                                  (gpointer)(ordering_dupl));                   
2153                 } 
2154         else 
2155                 {
2156                         g_free (tag_dupl);
2157                         g_free (ordering_dupl);
2158                 }
2159 }
2160
2161 /**
2162  * enchant_provider_set_error
2163  * @provider: A non-null provider
2164  * @err: A non-null error message
2165  * 
2166  * Sets the current runtime error to @err. This API is private to
2167  * the providers.
2168  */
2169 ENCHANT_MODULE_EXPORT(void)
2170 enchant_provider_set_error (EnchantProvider * provider, const char * const err)
2171 {
2172         EnchantBroker * broker;
2173
2174         g_return_if_fail (provider);
2175         g_return_if_fail (err);
2176         g_return_if_fail (g_utf8_validate(err, -1, NULL));
2177
2178         broker = provider->owner;
2179         g_return_if_fail (broker);
2180         
2181         enchant_broker_set_error (broker, err); 
2182 }
2183
2184 /**
2185  * enchant_broker_get_error
2186  * @broker: A non-null broker
2187  *
2188  * Returns a const char string or NULL describing the last exception in UTF8 encoding.
2189  * WARNING: error is transient and is likely cleared as soon as the 
2190  * next broker operation happens
2191  */
2192 ENCHANT_MODULE_EXPORT(char *)
2193 enchant_broker_get_error (EnchantBroker * broker)
2194 {
2195         g_return_val_if_fail (broker, NULL);
2196         
2197         return broker->error;
2198 }
2199
2200 /* private. returned string should be free'd with g_free */
2201 ENCHANT_MODULE_EXPORT(char *)
2202 enchant_get_user_language(void)
2203 {
2204         char * locale = NULL;
2205         
2206 #if defined(G_OS_WIN32)
2207         if(!locale)
2208                 locale = g_win32_getlocale ();
2209 #endif       
2210         
2211         if(!locale)
2212                 locale = g_strdup (g_getenv ("LANG"));
2213         
2214 #if defined(HAVE_LC_MESSAGES)
2215         if(!locale)
2216                 locale = g_strdup (setlocale (LC_MESSAGES, NULL));
2217 #endif
2218
2219         if(!locale)
2220                 locale = g_strdup (setlocale (LC_ALL, NULL));
2221
2222         if(!locale || strcmp(locale, "C") == 0) {
2223                 g_free(locale);
2224                 locale = g_strdup("en");
2225         }
2226                 
2227         return locale;
2228 }
2229
2230
2231 /**
2232  * enchant_get_prefix_dir
2233  *
2234  * Returns a string giving the location of the base directory
2235  * of the enchant installation.  This corresponds roughly to 
2236  * the --prefix option given to ./configure when enchant is
2237  * compiled, except it is determined at runtime based on the location
2238  * of the enchant library.
2239  *
2240  * Returns: the prefix dir if it can be determined, or %null otherwise. Must be free'd.
2241  *
2242  * This API is private to the providers.
2243  *
2244  */
2245 ENCHANT_MODULE_EXPORT (char *)
2246 enchant_get_prefix_dir(void)
2247 {
2248         char * prefix = NULL;
2249
2250 #ifdef _WIN32
2251         if (!prefix) {
2252                 /* Dynamically locate library and return containing directory */
2253                 WCHAR dll_path[MAX_PATH];
2254
2255                 if(GetModuleFileNameW(s_hModule,dll_path,MAX_PATH))
2256                         {
2257                                 gchar* utf8_dll_path = g_utf16_to_utf8 (dll_path, -1, NULL, NULL, NULL);
2258                                 prefix = g_path_get_dirname(utf8_dll_path);
2259                                 g_free(utf8_dll_path);
2260                                 /* Strip off "bin" subfolder if present */
2261                                 if (strlen(prefix) >=6 &&
2262                                     G_IS_DIR_SEPARATOR(prefix[strlen(prefix)-4]) &&
2263                                     g_ascii_strcasecmp(prefix+strlen(prefix)-3, "bin") == 0)
2264                                         prefix[strlen(prefix)-4] = '\0';
2265                         }
2266         }
2267 #endif
2268
2269 #if defined(ENABLE_BINRELOC)
2270         if (!prefix) {
2271                 /* Use standard binreloc PREFIX macro */
2272                 prefix = gbr_find_prefix(NULL);
2273         }
2274 #endif
2275
2276 #if defined(ENCHANT_PREFIX_DIR)
2277         if (!prefix) {
2278                 prefix = g_strdup (ENCHANT_PREFIX_DIR);
2279         }
2280 #endif
2281
2282         return prefix;
2283 }
2284
2285 ENCHANT_MODULE_EXPORT(char *)
2286 enchant_broker_get_param (EnchantBroker * broker, const char * const param_name) 
2287 {
2288         g_return_val_if_fail (broker, NULL);
2289         g_return_val_if_fail (param_name && *param_name, NULL);
2290
2291         return g_hash_table_lookup (broker->params, param_name);
2292 }
2293
2294 ENCHANT_MODULE_EXPORT(void)
2295 enchant_broker_set_param (EnchantBroker * broker, const char * const param_name, const char * const param_value)
2296 {
2297         g_return_if_fail (broker);
2298         g_return_if_fail (param_name && *param_name);
2299
2300         if (param_value == NULL || *param_value == '\0')
2301                 g_hash_table_remove (broker->params, param_name);
2302         else
2303                 g_hash_table_insert (broker->params, g_strdup (param_name), g_strdup (param_value));
2304 }
2305
2306 ENCHANT_MODULE_EXPORT (GSList *)
2307 enchant_get_dirs_from_param (EnchantBroker * broker, const char * const param_name)
2308 {
2309         const char *param_value;
2310         char **tokens;
2311         GSList *dirs = NULL;
2312
2313         param_value = enchant_broker_get_param (broker, param_name);
2314         if (param_value == NULL)
2315                 return NULL;
2316
2317 #ifdef _WIN32
2318         tokens = g_strsplit (param_value, ";", 0);
2319 #else
2320         tokens = g_strsplit (param_value, ":", 0);
2321 #endif
2322         if (tokens != NULL) {
2323                 int i;
2324                 for (i = 0; tokens[i]; i++) 
2325                         {
2326                                 char *token = g_strstrip(tokens[i]);
2327                                 dirs = g_slist_append (dirs, g_strdup (token));
2328                         }
2329                 
2330                 g_strfreev (tokens);            
2331         }
2332
2333         return dirs;
2334 }
2335
2336 ENCHANT_MODULE_EXPORT(char *)
2337 enchant_get_version (void) {
2338         return ENCHANT_VERSION_STRING;
2339 }