diff options
Diffstat (limited to 'e-util/e-spell-dictionary.c')
-rw-r--r-- | e-util/e-spell-dictionary.c | 797 |
1 files changed, 797 insertions, 0 deletions
diff --git a/e-util/e-spell-dictionary.c b/e-util/e-spell-dictionary.c new file mode 100644 index 0000000000..e6e06b7d9a --- /dev/null +++ b/e-util/e-spell-dictionary.c @@ -0,0 +1,797 @@ +/* + * e-spell-dictionary.c + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-spell-dictionary.h" +#include "e-spell-checker.h" + +#include <glib/gi18n-lib.h> +#include <string.h> + +#define E_SPELL_DICTIONARY_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE \ + ((obj), E_TYPE_SPELL_DICTIONARY, ESpellDictionaryPrivate)) + +/** + * ESpellDictionary: + * + * The #ESpellDictionary is a wrapper around #EnchantDict. + */ + +enum { + PROP_0, + PROP_SPELL_CHECKER +}; + +struct _ESpellDictionaryPrivate { + GWeakRef spell_checker; + + gchar *name; + gchar *code; + gchar *collate_key; +}; + +#define ISO_639_DOMAIN "iso_639" +#define ISO_3166_DOMAIN "iso_3166" + +static GHashTable *iso_639_table = NULL; +static GHashTable *iso_3166_table = NULL; + +G_DEFINE_TYPE ( + ESpellDictionary, + e_spell_dictionary, + G_TYPE_OBJECT); + +#ifdef HAVE_ISO_CODES + +#define ISOCODESLOCALEDIR ISO_CODES_PREFIX "/share/locale" + +#ifdef G_OS_WIN32 +#ifdef DATADIR +#undef DATADIR +#endif +#include <shlobj.h> +static HMODULE hmodule; + +BOOL WINAPI +DllMain (HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved); + +BOOL WINAPI +DllMain (HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + switch (fdwReason) + { + case DLL_PROCESS_ATTACH: + hmodule = hinstDLL; + break; + } + + return TRUE; +} + +static gchar * +_get_iso_codes_prefix (void) +{ + static gchar retval[1000]; + static gint beenhere = 0; + gchar *temp_dir = 0; + + if (beenhere) + return retval; + + if (!(temp_dir = g_win32_get_package_installation_directory_of_module ((gpointer) hmodule))) { + strcpy (retval, ISO_CODES_PREFIX); + return retval; + } + + strcpy (retval, temp_dir); + g_free (temp_dir); + beenhere = 1; + return retval; +} + +static gchar * +_get_isocodeslocaledir (void) +{ + static gchar retval[1000]; + static gint beenhere = 0; + + if (beenhere) + return retval; + + strcpy (retval, _get_iso_codes_prefix ()); + strcat (retval, "\\share\\locale" ); + beenhere = 1; + return retval; +} + +#undef ISO_CODES_PREFIX +#define ISO_CODES_PREFIX _get_iso_codes_prefix () + +#undef ISOCODESLOCALEDIR +#define ISOCODESLOCALEDIR _get_isocodeslocaledir () + +#endif + +static void +iso_639_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer data, + GError **error) +{ + GHashTable *hash_table = data; + const gchar *iso_639_1_code = NULL; + const gchar *iso_639_2_code = NULL; + const gchar *name = NULL; + const gchar *code = NULL; + gint ii; + + if (g_strcmp0 (element_name, "iso_639_entry") != 0) { + return; + } + + for (ii = 0; attribute_names[ii] != NULL; ii++) { + if (strcmp (attribute_names[ii], "name") == 0) + name = attribute_values[ii]; + else if (strcmp (attribute_names[ii], "iso_639_1_code") == 0) + iso_639_1_code = attribute_values[ii]; + else if (strcmp (attribute_names[ii], "iso_639_2T_code") == 0) + iso_639_2_code = attribute_values[ii]; + } + + code = (iso_639_1_code != NULL) ? iso_639_1_code : iso_639_2_code; + + if (code != NULL && *code != '\0' && name != NULL && *name != '\0') + g_hash_table_insert ( + hash_table, g_strdup (code), + g_strdup (dgettext (ISO_639_DOMAIN, name))); +} + +static void +iso_3166_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer data, + GError **error) +{ + GHashTable *hash_table = data; + const gchar *name = NULL; + const gchar *code = NULL; + gint ii; + + if (strcmp (element_name, "iso_3166_entry") != 0) + return; + + for (ii = 0; attribute_names[ii] != NULL; ii++) { + if (strcmp (attribute_names[ii], "name") == 0) + name = attribute_values[ii]; + else if (strcmp (attribute_names[ii], "alpha_2_code") == 0) + code = attribute_values[ii]; + } + + if (code != NULL && *code != '\0' && name != NULL && *name != '\0') + g_hash_table_insert ( + hash_table, g_ascii_strdown (code, -1), + g_strdup (dgettext (ISO_3166_DOMAIN, name))); +} + +static GMarkupParser iso_639_parser = { + iso_639_start_element, + NULL, NULL, NULL, NULL +}; + +static GMarkupParser iso_3166_parser = { + iso_3166_start_element, + NULL, NULL, NULL, NULL +}; + +static void +iso_codes_parse (const GMarkupParser *parser, + const gchar *basename, + GHashTable *hash_table) +{ + GMappedFile *mapped_file; + gchar *filename; + GError *error = NULL; + + filename = g_build_filename ( + ISO_CODES_PREFIX, "share", "xml", + "iso-codes", basename, NULL); + mapped_file = g_mapped_file_new (filename, FALSE, &error); + g_free (filename); + + if (mapped_file != NULL) { + GMarkupParseContext *context; + const gchar *contents; + gsize length; + + context = g_markup_parse_context_new ( + parser, 0, hash_table, NULL); + contents = g_mapped_file_get_contents (mapped_file); + length = g_mapped_file_get_length (mapped_file); + g_markup_parse_context_parse ( + context, contents, length, &error); + g_markup_parse_context_free (context); +#if GLIB_CHECK_VERSION(2,21,3) + g_mapped_file_unref (mapped_file); +#else + g_mapped_file_free (mapped_file); +#endif + } + + if (error != NULL) { + g_warning ("%s: %s", basename, error->message); + g_error_free (error); + } +} + +#endif /* HAVE_ISO_CODES */ + +struct _enchant_dict_description_data { + gchar *language_tag; + gchar *dict_name; +}; + +static void +describe_dictionary (const gchar *language_tag, + const gchar *provider_name, + const gchar *provider_desc, + const gchar *provider_file, + gpointer user_data) +{ + struct _enchant_dict_description_data *data = user_data; + const gchar *iso_639_name; + const gchar *iso_3166_name; + gchar *language_name; + gchar *lowercase; + gchar **tokens; + + /* Split language code into lowercase tokens. */ + lowercase = g_ascii_strdown (language_tag, -1); + tokens = g_strsplit (lowercase, "_", -1); + g_free (lowercase); + + g_return_if_fail (tokens != NULL); + + iso_639_name = g_hash_table_lookup (iso_639_table, tokens[0]); + + if (iso_639_name == NULL) { + language_name = g_strdup_printf ( + /* Translators: %s is the language ISO code. */ + C_("language", "Unknown (%s)"), language_tag); + goto exit; + } + + if (g_strv_length (tokens) < 2) { + language_name = g_strdup (iso_639_name); + goto exit; + } + + iso_3166_name = g_hash_table_lookup (iso_3166_table, tokens[1]); + + if (iso_3166_name != NULL) + language_name = g_strdup_printf ( + /* Translators: The first %s is the language name, and the + * second is the country name. Example: "French (France)" */ + C_("language", "%s (%s)"), iso_639_name, iso_3166_name); + else + language_name = g_strdup_printf ( + /* Translators: The first %s is the language name, and the + * second is the country name. Example: "French (France)" */ + C_("language", "%s (%s)"), iso_639_name, tokens[1]); + +exit: + g_strfreev (tokens); + + data->language_tag = g_strdup (language_tag); + data->dict_name = language_name; +} + +static void +spell_dictionary_set_enchant_dict (ESpellDictionary *dictionary, + EnchantDict *enchant_dict) +{ + struct _enchant_dict_description_data data; + + enchant_dict_describe (enchant_dict, describe_dictionary, &data); + + dictionary->priv->code = data.language_tag; + dictionary->priv->name = data.dict_name; + dictionary->priv->collate_key = g_utf8_collate_key (data.dict_name, -1); +} + +static void +spell_dictionary_set_spell_checker (ESpellDictionary *dictionary, + ESpellChecker *spell_checker) +{ + g_return_if_fail (E_IS_SPELL_CHECKER (spell_checker)); + + g_weak_ref_set (&dictionary->priv->spell_checker, spell_checker); +} + +static void +spell_dictionary_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_SPELL_CHECKER: + spell_dictionary_set_spell_checker ( + E_SPELL_DICTIONARY (object), + g_value_get_object (value)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +spell_dictionary_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) { + case PROP_SPELL_CHECKER: + g_value_take_object ( + value, + e_spell_dictionary_ref_spell_checker ( + E_SPELL_DICTIONARY (object))); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +spell_dictionary_dispose (GObject *object) +{ + ESpellDictionaryPrivate *priv; + + priv = E_SPELL_DICTIONARY_GET_PRIVATE (object); + + g_weak_ref_set (&priv->spell_checker, NULL); + + /* Chain up to parent's dispose() method. */ + G_OBJECT_CLASS (e_spell_dictionary_parent_class)->dispose (object); +} + +static void +spell_dictionary_finalize (GObject *object) +{ + ESpellDictionaryPrivate *priv; + + priv = E_SPELL_DICTIONARY_GET_PRIVATE (object); + + g_free (priv->name); + g_free (priv->code); + g_free (priv->collate_key); + + /* Chain up to parent's finalize() method. */ + G_OBJECT_CLASS (e_spell_dictionary_parent_class)->finalize (object); +} + +static void +e_spell_dictionary_class_init (ESpellDictionaryClass *class) +{ + GObjectClass *object_class; + + g_type_class_add_private (class, sizeof (ESpellDictionaryPrivate)); + + object_class = G_OBJECT_CLASS (class); + object_class->set_property = spell_dictionary_set_property; + object_class->get_property = spell_dictionary_get_property; + object_class->dispose = spell_dictionary_dispose; + object_class->finalize = spell_dictionary_finalize; + + g_object_class_install_property ( + object_class, + PROP_SPELL_CHECKER, + g_param_spec_object ( + "spell-checker", + NULL, + "Parent spell checker", + E_TYPE_SPELL_CHECKER, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +static void +e_spell_dictionary_init (ESpellDictionary *dictionary) +{ + dictionary->priv = E_SPELL_DICTIONARY_GET_PRIVATE (dictionary); + + if (!iso_639_table && !iso_3166_table) { +#if defined (ENABLE_NLS) && defined (HAVE_ISO_CODES) + bindtextdomain (ISO_639_DOMAIN, ISOCODESLOCALEDIR); + bind_textdomain_codeset (ISO_639_DOMAIN, "UTF-8"); + + bindtextdomain (ISO_3166_DOMAIN, ISOCODESLOCALEDIR); + bind_textdomain_codeset (ISO_3166_DOMAIN, "UTF-8"); +#endif /* ENABLE_NLS && HAVE_ISO_CODES */ + + iso_639_table = g_hash_table_new_full ( + (GHashFunc) g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + + iso_3166_table = g_hash_table_new_full ( + (GHashFunc) g_str_hash, + (GEqualFunc) g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_free); + +#ifdef HAVE_ISO_CODES + iso_codes_parse ( + &iso_639_parser, "iso_639.xml", iso_639_table); + iso_codes_parse ( + &iso_3166_parser, "iso_3166.xml", iso_3166_table); +#endif /* HAVE_ISO_CODES */ + } +} + +ESpellDictionary * +e_spell_dictionary_new (ESpellChecker *spell_checker, + EnchantDict *enchant_dict) +{ + ESpellDictionary *dictionary; + + g_return_val_if_fail (E_IS_SPELL_CHECKER (spell_checker), NULL); + g_return_val_if_fail (enchant_dict != NULL, NULL); + + dictionary = g_object_new ( + E_TYPE_SPELL_DICTIONARY, + "spell-checker", spell_checker, NULL); + + /* Since EnchantDict is not reference counted, ESpellChecker + * is loaning us the EnchantDict pointer. We do not own it. */ + spell_dictionary_set_enchant_dict (dictionary, enchant_dict); + + return dictionary; +} + +/** + * e_spell_dictionary_hash: + * @dictionary: an #ESpellDictionary + * + * Generates a hash value for @dictionary based on its ISO code. + * This function is intended for easily hashing an #ESpellDictionary + * to add to a #GHashTable or similar data structure. + * + * Returns: a hash value for @dictionary + **/ +guint +e_spell_dictionary_hash (ESpellDictionary *dictionary) +{ + const gchar *code; + + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), 0); + + code = e_spell_dictionary_get_code (dictionary); + + return g_str_hash (code); +} + +/** + * e_spell_dictionary_equal: + * @dictionary1: an #ESpellDictionary + * @dictionary2: another #ESpellDictionary + * + * Checks two #ESpellDictionary instances for equality based on their + * ISO codes. + * + * Returns: %TRUE if @dictionary1 and @dictionary2 are equal + **/ +gboolean +e_spell_dictionary_equal (ESpellDictionary *dictionary1, + ESpellDictionary *dictionary2) +{ + const gchar *code1, *code2; + + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary1), FALSE); + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary2), FALSE); + + if (dictionary1 == dictionary2) + return TRUE; + + code1 = e_spell_dictionary_get_code (dictionary1); + code2 = e_spell_dictionary_get_code (dictionary2); + + return g_str_equal (code1, code2); +} + +/** + * e_spell_dictionary_compare: + * @dictionary1: an #ESpellDictionary + * @dictionary2: another #ESpellDictionary + * + * Compares @dictionary1 and @dictionary2 by their display names for + * the purpose of lexicographical sorting. Use this function where a + * #GCompareFunc callback is required, such as g_list_sort(). + * + * Returns: 0 if the names match, + * a negative value if @dictionary1 < @dictionary2, + * or a positive value of @dictionary1 > @dictionary2 + **/ +gint +e_spell_dictionary_compare (ESpellDictionary *dictionary1, + ESpellDictionary *dictionary2) +{ + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary1), 0); + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary2), 0); + + return strcmp ( + dictionary1->priv->collate_key, + dictionary2->priv->collate_key); +} + +/** + * e_spell_dictionary_get_name: + * @dictionary: an #ESpellDictionary + * + * Returns the display name of the dictionary (for example + * "English (British)") + * + * Returns: the display name of the @dictionary + */ +const gchar * +e_spell_dictionary_get_name (ESpellDictionary *dictionary) +{ + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL); + + return dictionary->priv->name; +} + +/** + * e_spell_dictionary_get_code: + * @dictionary: an #ESpellDictionary + * + * Returns the ISO code of the spell-checking language for + * @dictionary (for example "en_US"). + * + * Returns: the language code of the @dictionary + */ +const gchar * +e_spell_dictionary_get_code (ESpellDictionary *dictionary) +{ + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL); + + return dictionary->priv->code; +} + +/** + * e_spell_dictionary_ref_spell_checker: + * @dictionary: an #ESpellDictionary + * + * Returns a new reference to the #ESpellChecker which owns the dictionary. + * Unreference the #ESpellChecker with g_object_unref() when finished with it. + * + * Returns: an #ESpellChecker + **/ +ESpellChecker * +e_spell_dictionary_ref_spell_checker (ESpellDictionary *dictionary) +{ + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL); + + return g_weak_ref_get (&dictionary->priv->spell_checker); +} + +/** + * e_spell_dictionary_check_word: + * @dictionary: an #ESpellDictionary + * @word: a word to spell-check + * @length: length of @word in bytes or -1 when %NULL-terminated + * + * Tries to lookup the @word in the @dictionary to check whether + * it's spelled correctly or not. + * + * Returns: %TRUE if @word is recognized, %FALSE otherwise + */ +gboolean +e_spell_dictionary_check_word (ESpellDictionary *dictionary, + const gchar *word, + gsize length) +{ + ESpellChecker *spell_checker; + EnchantDict *enchant_dict; + gboolean recognized; + + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), TRUE); + g_return_val_if_fail (word != NULL && *word != '\0', TRUE); + + spell_checker = e_spell_dictionary_ref_spell_checker (dictionary); + g_return_val_if_fail (spell_checker != NULL, TRUE); + + enchant_dict = e_spell_checker_get_enchant_dict ( + spell_checker, e_spell_dictionary_get_code (dictionary)); + g_return_val_if_fail (enchant_dict != NULL, TRUE); + + recognized = (enchant_dict_check (enchant_dict, word, length) == 0); + + g_object_unref (spell_checker); + + return recognized; +} + +/** + * e_spell_dictionary_learn_word: + * @dictionary: an #ESpellDictionary + * @word: a word to add to @dictionary + * @length: length of @word in bytes or -1 when %NULL-terminated + * + * Permanently adds @word to @dictionary so that next time calling + * e_spell_dictionary_check() on the @word will return %TRUE. + */ +void +e_spell_dictionary_learn_word (ESpellDictionary *dictionary, + const gchar *word, + gsize length) +{ + ESpellChecker *spell_checker; + EnchantDict *enchant_dict; + + g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary)); + g_return_if_fail (word != NULL && *word != '\0'); + + spell_checker = e_spell_dictionary_ref_spell_checker (dictionary); + g_return_if_fail (spell_checker != NULL); + + enchant_dict = e_spell_checker_get_enchant_dict ( + spell_checker, e_spell_dictionary_get_code (dictionary)); + g_return_if_fail (enchant_dict != NULL); + + enchant_dict_add_to_personal (enchant_dict, word, length); + + g_object_unref (spell_checker); +} + +/** + * e_spell_dictionary_ignore_word: + * @dictionary: an #ESpellDictionary + * @word: a word to add to ignore list + * @length: length of @word in bytes or -1 when %NULL-terminated + * + * Adds @word to temporary ignore list of the @dictionary, so that + * e_spell_dictionary_check() on the @word will return %TRUE. The + * list is cleared when the dictionary is freed. + */ +void +e_spell_dictionary_ignore_word (ESpellDictionary *dictionary, + const gchar *word, + gsize length) +{ + ESpellChecker *spell_checker; + EnchantDict *enchant_dict; + + g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary)); + g_return_if_fail (word != NULL && *word != '\0'); + + spell_checker = e_spell_dictionary_ref_spell_checker (dictionary); + g_return_if_fail (spell_checker != NULL); + + enchant_dict = e_spell_checker_get_enchant_dict ( + spell_checker, e_spell_dictionary_get_code (dictionary)); + g_return_if_fail (enchant_dict != NULL); + + enchant_dict_add_to_session (enchant_dict, word, length); + + g_object_unref (spell_checker); +} + +/** + * e_spell_dictionary_get_suggestions: + * @dictionary: an #ESpellDictionary + * @word: a word to which to find suggestions + * @length: length of @word in bytes or -1 when %NULL-terminated + * + * Provides list of alternative spellings of @word. + * + * Free the returned spelling suggestions with g_free(), and the list + * itself with g_list_free(). An easy way to free the list properly in + * one step is as follows: + * + * |[ + * g_list_free_full (list, (GDestroyNotify) g_free); + * ]| + * + * Returns: a list of spelling suggestions for @word + */ +GList * +e_spell_dictionary_get_suggestions (ESpellDictionary *dictionary, + const gchar *word, + gsize length) +{ + ESpellChecker *spell_checker; + EnchantDict *enchant_dict; + GList *list = NULL; + gchar **suggestions; + gsize ii, count = 0; + + g_return_val_if_fail (E_IS_SPELL_DICTIONARY (dictionary), NULL); + g_return_val_if_fail (word != NULL && *word != '\0', NULL); + + spell_checker = e_spell_dictionary_ref_spell_checker (dictionary); + g_return_val_if_fail (spell_checker != NULL, NULL); + + enchant_dict = e_spell_checker_get_enchant_dict ( + spell_checker, e_spell_dictionary_get_code (dictionary)); + g_return_val_if_fail (enchant_dict != NULL, NULL); + + suggestions = enchant_dict_suggest (enchant_dict, word, length, &count); + for (ii = 0; ii < count; ii++) + list = g_list_prepend (list, g_strdup (suggestions[ii])); + enchant_dict_free_suggestions (enchant_dict, suggestions); + + g_object_unref (spell_checker); + + return g_list_reverse (list); +} + +/** + * e_spell_dictionary_add_correction + * @dictionary: an #ESpellDictionary + * @misspelled: a misspelled word + * @misspelled_length: length of @misspelled in bytes or -1 when + * %NULL-terminated + * @correction: the corrected word + * @correction_length: length of @correction in bytes or -1 when + * %NULL-terminated + * + * Learns a new @correction of @misspelled word. + */ +void +e_spell_dictionary_store_correction (ESpellDictionary *dictionary, + const gchar *misspelled, + gsize misspelled_length, + const gchar *correction, + gsize correction_length) +{ + ESpellChecker *spell_checker; + EnchantDict *enchant_dict; + + g_return_if_fail (E_IS_SPELL_DICTIONARY (dictionary)); + g_return_if_fail (misspelled != NULL && *misspelled != '\0'); + g_return_if_fail (correction != NULL && *correction != '\0'); + + spell_checker = e_spell_dictionary_ref_spell_checker (dictionary); + g_return_if_fail (spell_checker != NULL); + + enchant_dict = e_spell_checker_get_enchant_dict ( + spell_checker, e_spell_dictionary_get_code (dictionary)); + g_return_if_fail (enchant_dict != NULL); + + enchant_dict_store_replacement ( + enchant_dict, + misspelled, misspelled_length, + correction, correction_length); + + g_object_unref (spell_checker); +} + |