]> git.0d.be Git - empathy.git/blob - libempathy-gtk/gossip-spell.c
[darcs-to-svn @ GossipMainWindow]
[empathy.git] / libempathy-gtk / gossip-spell.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Copyright (C) 2004-2007 Imendio AB
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program 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  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Authors: Martyn Russell <martyn@imendio.com>
21  *          Richard Hult <richard@imendio.com>
22  */
23
24 #include "config.h"
25
26 #include <string.h>
27 #include <stdlib.h>
28
29 #include <glib/gi18n.h>
30
31 #ifdef HAVE_ASPELL
32 #include <aspell.h>
33 #endif
34
35 #include <libempathy/gossip-debug.h>
36 #include <libempathy/gossip-conf.h>
37
38 #include "gossip-spell.h"
39 #include "gossip-preferences.h"
40
41 #define DEBUG_DOMAIN "Spell"
42
43 #ifdef HAVE_ASPELL
44
45 /* Note: We could use aspell_reset_cache (NULL); periodically if we wanted
46  * to...
47  */
48
49 typedef struct {
50         AspellConfig       *spell_config;
51         AspellCanHaveError *spell_possible_err;
52         AspellSpeller      *spell_checker;
53 } SpellLanguage;
54
55 #define ISO_CODES_DATADIR    ISO_CODES_PREFIX "/share/xml/iso-codes"
56 #define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
57
58 static GHashTable  *iso_code_names = NULL;
59 static GList       *languages = NULL;
60 static gboolean     gossip_conf_notify_inited = FALSE;
61
62 static void
63 spell_iso_codes_parse_start_tag (GMarkupParseContext  *ctx,
64                                  const gchar          *element_name,
65                                  const gchar         **attr_names,
66                                  const gchar         **attr_values,
67                                  gpointer              data,
68                                  GError              **error)
69 {
70         const gchar *ccode_longB, *ccode_longT, *ccode;
71         const gchar *lang_name;
72
73         if (!g_str_equal (element_name, "iso_639_entry") ||
74             attr_names == NULL || attr_values == NULL) {
75                 return;
76         }
77
78         ccode = NULL;
79         ccode_longB = NULL;
80         ccode_longT = NULL;
81         lang_name = NULL;
82
83         while (*attr_names && *attr_values) {
84                 if (g_str_equal (*attr_names, "iso_639_1_code")) {
85                         if (**attr_values) {
86                                 ccode = *attr_values;
87                         }
88                 }
89                 else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
90                         if (**attr_values) {
91                                 ccode_longB = *attr_values;
92                         }
93                 }
94                 else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
95                         if (**attr_values) {
96                                 ccode_longT = *attr_values;
97                         }
98                 }
99                 else if (g_str_equal (*attr_names, "name")) {
100                         lang_name = *attr_values;
101                 }
102
103                 attr_names++;
104                 attr_values++;
105         }
106
107         if (!lang_name) {
108                 return;
109         }
110
111         if (ccode) {
112                 g_hash_table_insert (iso_code_names,
113                                      g_strdup (ccode),
114                                      g_strdup (lang_name));
115         }
116
117         if (ccode_longB) {
118                 g_hash_table_insert (iso_code_names,
119                                      g_strdup (ccode_longB),
120                                      g_strdup (lang_name));
121         }
122
123         if (ccode_longT) {
124                 g_hash_table_insert (iso_code_names,
125                                      g_strdup (ccode_longT),
126                                      g_strdup (lang_name));
127         }
128 }
129
130 static void
131 spell_iso_code_names_init (void)
132 {
133         GError *err = NULL;
134         gchar  *buf;
135         gsize   buf_len;
136
137         iso_code_names = g_hash_table_new_full (g_str_hash, g_str_equal,
138                                                 g_free, g_free);
139
140         bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
141         bind_textdomain_codeset ("iso_639", "UTF-8");
142
143         /* FIXME: We should read this in chunks and pass to the parser. */
144         if (g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml", &buf, &buf_len, &err)) {
145                 GMarkupParseContext *ctx;
146                 GMarkupParser        parser = {
147                         spell_iso_codes_parse_start_tag,
148                         NULL, NULL, NULL, NULL
149                 };
150
151                 ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
152                 if (!g_markup_parse_context_parse (ctx, buf, buf_len, &err)) {
153                         g_warning ("Failed to parse '%s': %s",
154                                    ISO_CODES_DATADIR"/iso_639.xml",
155                                    err->message);
156                         g_error_free (err);
157                 }
158
159                 g_markup_parse_context_free (ctx);
160                 g_free (buf);
161         } else {
162                 g_warning ("Failed to load '%s': %s",
163                                 ISO_CODES_DATADIR"/iso_639.xml", err->message);
164                 g_error_free (err);
165         }
166 }
167
168 static void
169 spell_notify_languages_cb (GossipConf  *conf,
170                            const gchar *key,
171                            gpointer     user_data)
172 {
173         GList *l;
174
175         gossip_debug (DEBUG_DOMAIN, "Resetting languages due to config change");
176
177         /* We just reset the languages list. */
178         for (l = languages; l; l = l->next) {
179                 SpellLanguage *lang;
180
181                 lang = l->data;
182
183                 delete_aspell_config (lang->spell_config);
184                 delete_aspell_speller (lang->spell_checker);
185
186                 g_slice_free (SpellLanguage, lang);
187         }
188
189         g_list_free (languages);
190         languages = NULL;
191 }
192
193 static void
194 spell_setup_languages (void)
195 {
196         gchar  *str;
197
198         if (!gossip_conf_notify_inited) {
199                 gossip_conf_notify_add (gossip_conf_get (),
200                                          GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
201                                          spell_notify_languages_cb, NULL);
202
203                 gossip_conf_notify_inited = TRUE;
204         }
205
206         if (languages) {
207                 gossip_debug (DEBUG_DOMAIN, "No languages to setup");
208                 return;
209         }
210
211         if (gossip_conf_get_string (gossip_conf_get (),
212                                      GOSSIP_PREFS_CHAT_SPELL_CHECKER_LANGUAGES,
213                                      &str) && str) {
214                 gchar **strv;
215                 gint    i;
216
217                 strv = g_strsplit (str, ",", -1);
218
219                 i = 0;
220                 while (strv && strv[i]) {
221                         SpellLanguage *lang;
222
223                         gossip_debug (DEBUG_DOMAIN, "Setting up language:'%s'", strv[i]);
224
225                         lang = g_slice_new0 (SpellLanguage);
226
227                         lang->spell_config = new_aspell_config();
228
229                         aspell_config_replace (lang->spell_config, "encoding", "utf-8");
230                         aspell_config_replace (lang->spell_config, "lang", strv[i++]);
231
232                         lang->spell_possible_err = new_aspell_speller (lang->spell_config);
233
234                         if (aspell_error_number (lang->spell_possible_err) == 0) {
235                                 lang->spell_checker = to_aspell_speller (lang->spell_possible_err);
236                                 languages = g_list_append (languages, lang);
237                         } else {
238                                 delete_aspell_config (lang->spell_config);
239                                 g_slice_free (SpellLanguage, lang);
240                         }
241                 }
242
243                 if (strv) {
244                         g_strfreev (strv);
245                 }
246
247                 g_free (str);
248         }
249 }
250
251 const char *
252 gossip_spell_get_language_name (const char *code)
253 {
254         const gchar *name;
255
256         g_return_val_if_fail (code != NULL, NULL);
257
258         if (!iso_code_names) {
259                 spell_iso_code_names_init ();
260         }
261
262         name = g_hash_table_lookup (iso_code_names, code);
263         if (!name) {
264                 return NULL;
265         }
266
267         return dgettext ("iso_639", name);
268 }
269
270 GList *
271 gossip_spell_get_language_codes (void)
272 {
273         AspellConfig              *config;
274         AspellDictInfoList        *dlist;
275         AspellDictInfoEnumeration *dels;
276         const AspellDictInfo      *entry;
277         GList                     *codes = NULL;
278
279         config = new_aspell_config ();
280         dlist = get_aspell_dict_info_list (config);
281         dels = aspell_dict_info_list_elements (dlist);
282
283         while ((entry = aspell_dict_info_enumeration_next (dels)) != 0) {
284                 if (g_list_find_custom (codes, entry->code, (GCompareFunc) strcmp)) {
285                         continue;
286                 }
287
288                 codes = g_list_append (codes, g_strdup (entry->code));
289         }
290
291         delete_aspell_dict_info_enumeration (dels);
292         delete_aspell_config (config);
293
294         return codes;
295 }
296
297 void
298 gossip_spell_free_language_codes (GList *codes)
299 {
300         g_list_foreach (codes, (GFunc) g_free, NULL);
301         g_list_free (codes);
302 }
303
304 gboolean
305 gossip_spell_check (const gchar *word)
306 {
307         GList       *l;
308         gint         n_langs;
309         gboolean     correct = FALSE;
310         gint         len;
311         const gchar *p;
312         gunichar     c;
313         gboolean     digit;
314
315         g_return_val_if_fail (word != NULL, FALSE);
316
317         spell_setup_languages ();
318
319         if (!languages) {
320                 gossip_debug (DEBUG_DOMAIN, "No languages to check against");
321                 return TRUE;
322         }
323
324         /* Ignore certain cases like numbers, etc. */
325         for (p = word, digit = TRUE; *p && digit; p = g_utf8_next_char (p)) {
326                 c = g_utf8_get_char (p);
327                 digit = g_unichar_isdigit (c);
328         }
329
330         if (digit) {
331                 /* We don't spell check digits. */
332                 gossip_debug (DEBUG_DOMAIN, "Not spell checking word:'%s', it is all digits", word);
333                 return TRUE;
334         }
335
336         len = strlen (word);
337         n_langs = g_list_length (languages);
338         for (l = languages; l; l = l->next) {
339                 SpellLanguage *lang;
340
341                 lang = l->data;
342
343                 correct = aspell_speller_check (lang->spell_checker, word, len);
344                 if (n_langs > 1 && correct) {
345                         break;
346                 }
347         }
348
349         return correct;
350 }
351
352 GList *
353 gossip_spell_get_suggestions (const gchar *word)
354 {
355         GList                   *l1;
356         GList                   *l2 = NULL;
357         const AspellWordList    *suggestions;
358         AspellStringEnumeration *elements;
359         const char              *next;
360         gint                     len;
361
362         g_return_val_if_fail (word != NULL, NULL);
363
364         spell_setup_languages ();
365
366         len = strlen (word);
367
368         for (l1 = languages; l1; l1 = l1->next) {
369                 SpellLanguage *lang;
370
371                 lang = l1->data;
372
373                 suggestions = aspell_speller_suggest (lang->spell_checker,
374                                                       word, len);
375
376                 elements = aspell_word_list_elements (suggestions);
377
378                 while ((next = aspell_string_enumeration_next (elements))) {
379                         l2 = g_list_append (l2, g_strdup (next));
380                 }
381
382                 delete_aspell_string_enumeration (elements);
383         }
384
385         return l2;
386 }
387
388 gboolean
389 gossip_spell_supported (void)
390 {
391         if (g_getenv ("GOSSIP_SPELL_DISABLED")) {
392                 gossip_debug (DEBUG_DOMAIN, "GOSSIP_SPELL_DISABLE env variable defined");
393                 return FALSE;
394         }
395
396         gossip_debug (DEBUG_DOMAIN, "Support enabled");
397
398         return TRUE;
399 }
400
401 #else /* not HAVE_ASPELL */
402
403 gboolean
404 gossip_spell_supported (void)
405 {
406         gossip_debug (DEBUG_DOMAIN, "Support disabled");
407
408         return FALSE;
409 }
410
411 GList *
412 gossip_spell_get_suggestions (const gchar *word)
413 {
414         gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get suggestions");
415
416         return NULL;
417 }
418
419 gboolean
420 gossip_spell_check (const gchar *word)
421 {
422         gossip_debug (DEBUG_DOMAIN, "Support disabled, could not check spelling");
423
424         return TRUE;
425 }
426
427 const char *
428 gossip_spell_get_language_name (const char *lang)
429 {
430         gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language name");
431
432         return NULL;
433 }
434
435 GList *
436 gossip_spell_get_language_codes (void)
437 {
438         gossip_debug (DEBUG_DOMAIN, "Support disabled, could not get language codes");
439
440         return NULL;
441 }
442
443 void
444 gossip_spell_free_language_codes (GList *codes)
445 {
446 }
447
448 #endif /* HAVE_ASPELL */
449
450
451 void
452 gossip_spell_free_suggestions (GList *suggestions)
453 {
454         g_list_foreach (suggestions, (GFunc) g_free, NULL);
455         g_list_free (suggestions);
456 }
457