<commit_before>/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* enchant
 * Copyright (C) 2003, 2004 <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02110-1301, USA.
 *
 * In addition, as a special exception, <NAME>
 * gives permission to link the code of this program with
 * non-LGPL Spelling Provider libraries (eg: a MSFT Office
 * spell checker backend) and distribute linked combinations including
 * the two.  You must obey the GNU Lesser General Public License in all
 * respects for all of the code used other than said providers.  If you modify
 * this file, you may extend this exception to your version of the
 * file, but you are not obligated to do so.  If you do not wish to
 * do so, delete this exception statement from your version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <glib.h>
#include <gmodule.h>
#include <glib/gstdio.h>
#include <locale.h>

#include "enchant.h"
#include "enchant-provider.h"
#include "pwl.h"

#ifdef XP_TARGET_COCOA
#import "enchant_cocoa.h"
#endif

#ifdef XP_TARGET_COCOA
#define ENCHANT_USER_PATH_EXTENSION "Library", "Application Support", "Enchant"
#elif defined(_WIN32)
#define ENCHANT_USER_PATH_EXTENSION "enchant"
#else
#define ENCHANT_USER_PATH_EXTENSION ".enchant"
#endif

#ifdef ENABLE_BINRELOC
#include "prefix.h"
#endif

ENCHANT_PLUGIN_DECLARE("Enchant")

static char *
enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key);

/********************************************************************************/
/********************************************************************************/

struct str_enchant_broker
{
	GSList *provider_list;	/* list of all of the spelling backend providers */
	GHashTable *dict_map;		/* map of language tag -> dictionary */
	GHashTable *provider_ordering; /* map of language tag -> provider order */
	GHashTable *params;

	gchar * error;
};

typedef struct str_enchant_session
{
	GHashTable *session_include;
	GHashTable *session_exclude;
	EnchantPWL *personal;
	EnchantPWL *exclude;

	char * personal_filename;
	char * exclude_filename;
	char * language_tag;

	char * error;

	gboolean is_pwl;

	EnchantProvider * provider;
} EnchantSession;

typedef struct str_enchant_dict_private_data
{
	unsigned int reference_count;
	EnchantSession* session;
} EnchantDictPrivateData;

typedef EnchantProvider *(*EnchantProviderInitFunc) (void);
typedef void             (*EnchantPreConfigureFunc) (EnchantProvider * provider, const char * module_dir);

/********************************************************************************/
/********************************************************************************/

#ifdef _WIN32
#define path_cmp g_utf8_collate
#else
#define path_cmp strcmp
#endif

static GSList* enchant_slist_prepend_unique_path (GSList *slist, gchar* data)
{
	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
		{
			return g_slist_prepend (slist, data);
		}
	else
		{
			g_free (data);
			return slist;
		}
}

static GSList* enchant_slist_append_unique_path (GSList *slist, gchar* data)
{
	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
		{
			return g_slist_append (slist, data);
		}
	else
		{
			g_free (data);
			return slist;
		}
}

static GSList *
_enchant_get_user_home_dirs (void)
{
	GSList *dirs = NULL;
	const char* home_dir;
	char *tmp;

	tmp = enchant_get_registry_value_ex (1, "Config", "Home_Dir");
	if (tmp)
		dirs = enchant_slist_append_unique_path (dirs, tmp);

	home_dir = g_get_home_dir ();
	if (home_dir)
		dirs = enchant_slist_append_unique_path (dirs, g_strdup (home_dir));

	return dirs;
}

static void
_enchant_ensure_dir_exists (const char* dir)
{
	if (dir && !g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) 
		{
			(void)g_remove (dir);
			g_mkdir_with_parents (dir, 0700);                        
		}
}

static GSList *
enchant_get_user_dirs (void)
{
	GSList *user_dirs = NULL;

	{
		const char * user_config_dir;
		
		user_config_dir = g_get_user_config_dir();
		
		if (user_config_dir)
			user_dirs = enchant_slist_append_unique_path (user_dirs, g_build_filename (user_config_dir,
										 "enchant",
										 NULL));
	}

	{
		GSList *home_dirs = NULL, *dir;
		home_dirs = _enchant_get_user_home_dirs ();
		
		for (dir = home_dirs; dir; dir = dir->next)
			{
				user_dirs = enchant_slist_append_unique_path (user_dirs,
							    g_build_filename (dir->data,
									      ENCHANT_USER_PATH_EXTENSION,
									      NULL));
			}

		g_slist_foreach (home_dirs, (GFunc)g_free, NULL);
		g_slist_free (home_dirs);
	}

	return user_dirs;
}

/* place to look for system level providers */
static GSList *
enchant_get_module_dirs (void)
{
	GSList *module_dirs = NULL;

	char * module_dir = NULL;
	char * prefix = NULL;

	{
		char* user_module_dir;
		
		user_module_dir = enchant_get_registry_value_ex (1, "Config", "Module_Dir");
		if (user_module_dir)
			module_dirs = enchant_slist_append_unique_path (module_dirs, user_module_dir);
	}

#ifdef XP_TARGET_COCOA
	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup ([[EnchantResourceProvider instance] moduleFolder]));
#endif

	{
		GSList *user_dirs, *iter;

		user_dirs = enchant_get_user_dirs();

		for (iter = user_dirs; iter; iter = iter->next)
			module_dirs = enchant_slist_append_unique_path (module_dirs, iter->data);
		
		g_slist_free (user_dirs);
	}

	/* Look for explicitly set registry values */
	module_dir = enchant_get_registry_value_ex (0, "Config", "Module_Dir");
	if (module_dir)
		module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);

#if defined(ENCHANT_GLOBAL_MODULE_DIR)
	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup (ENCHANT_GLOBAL_MODULE_DIR));
#endif
	/* Dynamically locate library and search for modules relative to it. */
	prefix = enchant_get_prefix_dir();
	if(prefix)
		{
			module_dir = g_build_filename(prefix,"lib","enchant",NULL);
			g_free(prefix);
			module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);
		}

	return module_dirs;
}

static GSList *
enchant_get_conf_dirs (void)
{
	GSList *conf_dirs = NULL, *user_conf_dirs, *iter;
	char * ordering_dir = NULL, * prefix = NULL;

	user_conf_dirs = enchant_get_user_config_dirs();

	for (iter = user_conf_dirs; iter != NULL; iter = iter->next)
		{
			conf_dirs = enchant_slist_append_unique_path (conf_dirs, iter->data);
		}

	g_slist_free (user_conf_dirs);

#ifdef XP_TARGET_COCOA
	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup ([[EnchantResourceProvider instance] configFolder]));
#endif

	/* Look for explicitly set registry values */
	ordering_dir = enchant_get_registry_value_ex (0, "Config", "Data_Dir");
	if (ordering_dir)
		conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);

	/* Dynamically locate library and search for files relative to it. */
	prefix = enchant_get_prefix_dir();
	if(prefix)
		{
			ordering_dir = g_build_filename(prefix,"share","enchant",NULL);
			g_free(prefix);
			conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);
		}

#if defined(ENCHANT_GLOBAL_ORDERING)
	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup (ENCHANT_GLOBAL_ORDERING));
#endif

	return conf_dirs;
}

ENCHANT_MODULE_EXPORT(FILE *)
enchant_fopen (const gchar *filename, const gchar *mode)
{
#ifdef G_OS_WIN32
  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
  wchar_t *wmode;
  FILE *retval;
  int save_errno;

  if (wfilename == NULL)
    {
      errno = EINVAL;
      return NULL;
    }

  wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL);

  if (wmode == NULL)
    {
      g_free (wfilename);
      errno = EINVAL;
      return NULL;
    }

  retval = _wfopen (wfilename, wmode);
  save_errno = errno;

  g_free (wfilename);
  g_free (wmode);

  errno = save_errno;
  return retval;
#else
  return fopen (filename, mode);
#endif
}

/**
 * enchant_get_user_config_dir
 *
 * Returns: the user's enchant directory, or %null. Returned value
 * must be free'd.
 *
 * The enchant directory is the place where enchant finds user 
 * dictionaries and settings related to enchant
 *
 * This API is private to the providers.
 */
ENCHANT_MODULE_EXPORT (GSList *)
enchant_get_user_config_dirs (void)
{
	GSList *dirs;
	char* user_config;

	dirs = enchant_get_user_dirs();

	user_config = enchant_get_registry_value_ex (1, "Config", "Data_Dir");
	if (user_config)
		dirs = enchant_slist_prepend_unique_path (dirs, user_config);

	return dirs;
}

/*
 * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
 */
static char *
enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key)
{
#ifndef _WIN32
	/* TODO: GConf? KConfig? */
	return NULL;
#else
	HKEY hKey;
	HKEY baseKey;
	unsigned long lType;	
	DWORD dwSize;
	char* keyName;
	WCHAR* wszValue = NULL;
	char* szValue = NULL;
	gunichar2 * uKeyName;
	gunichar2 * uKey;

	if (current_user)
		baseKey = HKEY_CURRENT_USER;
	else
		baseKey = HKEY_LOCAL_MACHINE;

	keyName = g_strdup_printf("Software\\Enchant\\%s", prefix);
	uKeyName = g_utf8_to_utf16 (keyName, -1, NULL, NULL, NULL);
	uKey = g_utf8_to_utf16 (key, -1, NULL, NULL, NULL);

	if(RegOpenKeyExW(baseKey, uKeyName, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
		{
			/* Determine size of string */
			if(RegQueryValueExW( hKey, uKey, NULL, &lType, NULL, &dwSize) == ERROR_SUCCESS)
				{
					wszValue = g_new0(WCHAR, dwSize + 1);
					RegQueryValueExW(hKey, uKey, NULL, &lType, (LPBYTE) wszValue, &dwSize);
				}
			RegCloseKey(hKey);
		}

	if(wszValue && *wszValue)
		szValue = g_utf16_to_utf8 (wszValue, -1, NULL, NULL, NULL);

	g_free(keyName);
	g_free(uKeyName);
	g_free(uKey);
	g_free(wszValue);

	return szValue;
#endif
}

/**
 * enchant_get_registry_value
 * @prefix: Your category, such as "Ispell" or "Myspell"
 * @key: The tag within your category that you're interested in
 *
 * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
 *
 * This API is private to the providers.
 */
ENCHANT_MODULE_EXPORT (char *)
enchant_get_registry_value (const char * const prefix, const char * const key)
{
	char *val;

	g_return_val_if_fail (prefix, NULL);
	g_return_val_if_fail (key, NULL);

	val = enchant_get_registry_value_ex(1, prefix, key);
	if(val == NULL) {
			val = enchant_get_registry_value_ex (0, prefix, key);
		}
	return val;
}

/********************************************************************************/
/********************************************************************************/

static gchar*
enchant_modify_string_chars (gchar *str,
							 gssize len,
							 gchar (*function)(gchar))
{
	gchar* it, *end;

	g_return_val_if_fail (str != NULL, NULL);

	if (len < 0)
		len = strlen (str);

	end = str + len;

	for (it = str; it != end; ++it)
		*it = function (*it);

	return str;
}

static gchar*
enchant_ascii_strup (gchar *str,
					 gssize len)
{
	return enchant_modify_string_chars(str, len, g_ascii_toupper);
}

static gchar*
enchant_ascii_strdown (gchar *str,
						  gssize len)
{
	return enchant_modify_string_chars(str, len, g_ascii_tolower);
}

/* returns TRUE if tag is valid
 * for requires alphanumeric ASCII or underscore
 */
static int
enchant_is_valid_dictionary_tag(const char * const tag)
{
	const char * it;
	for (it = tag; *it; ++it)
		{
			if(!g_ascii_isalnum(*it) && *it != '_')
				return 0;
		}

	return it != tag; /*empty tag invalid*/
}

static char *
enchant_normalize_dictionary_tag (const char * const dict_tag)
{
	char * new_tag = g_strdup (dict_tag);
	char * needle;

	new_tag = g_strstrip (new_tag);

	/* strip off en_GB@euro */
	if ((needle = strchr (new_tag, '@')) != NULL)
		*needle = '\0';

	/* strip off en_GB.UTF-8 */
	if ((needle = strchr (new_tag, '.')) != NULL)
		*needle = '\0';

	/* turn en-GB into en_GB */
	if ((needle = strchr (new_tag, '-')) != NULL)
		*needle = '_';

	/* everything before first '_' is converted to lower case */
	if ((needle = strchr (new_tag, '_')) != NULL) {
			enchant_ascii_strdown(new_tag, needle - new_tag);
			++needle;
			/* everything after first '_' is converted to upper case */
			enchant_ascii_strup(needle, -1);
		}
	else {
			enchant_ascii_strdown(new_tag, -1);
		}

	return new_tag;
}

static char *
enchant_iso_639_from_tag (const char * const dict_tag)
{
	char * new_tag = g_strdup (dict_tag);
	char * needle;

	if ((needle = strchr (new_tag, '_')) != NULL)
		*needle = '\0';

	return new_tag;
}

static void
enchant_session_destroy (EnchantSession * session)
{
	g_hash_table_destroy (session->session_include);
	g_hash_table_destroy (session->session_exclude);
	enchant_pwl_free (session->personal);
	enchant_pwl_free (session->exclude);
	g_free (session->personal_filename);
	g_free (session->exclude_filename);
	g_free (session->language_tag);

	if (session->error)
		g_free (session->error);

	g_free (session);
}

static EnchantSession *
enchant_session_new_with_pwl (EnchantProvider * provider, 
							  const char * const pwl, 
							  const char * const excl,
							  const char * const lang,
							  gboolean fail_if_no_pwl)
{
	EnchantSession * session;
	EnchantPWL *personal = NULL;
	EnchantPWL *exclude = NULL;

	if (pwl)
		personal = enchant_pwl_init_with_file (pwl);

	if (personal == NULL) {
		if (fail_if_no_pwl)
			return NULL;
		else
			personal = enchant_pwl_init ();
	}
	
	if (excl)
		exclude = enchant_pwl_init_with_file (excl);
	if (exclude == NULL)
		exclude = enchant_pwl_init ();

	session = g_new0 (EnchantSession, 1);
	session->session_include = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	session->session_exclude = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	session->personal = personal;
	session->exclude = exclude;
	session->provider = provider;
	session->language_tag = g_strdup (lang);
	session->personal_filename = g_strdup (pwl);
	session->exclude_filename = g_strdup (excl);
	
	return session;
}

static EnchantSession *
_enchant_session_new (EnchantProvider *provider, const char * const user_config_dir, 
		      const char * const lang, gboolean fail_if_no_pwl)
{
	char *filename, *dic, *excl;
	EnchantSession * session;

	if (!user_config_dir || !lang)
		return NULL;
	
	filename = g_strdup_printf ("%s.dic", lang);
	dic = g_build_filename (user_config_dir, filename, NULL);
	g_free (filename);
	
	filename = g_strdup_printf ("%s.exc", lang);
	excl = g_build_filename (user_config_dir, filename,	NULL);
	g_free (filename);
	
	session = enchant_session_new_with_pwl (provider, dic, excl, lang, fail_if_no_pwl);
	
	g_free (dic);
	g_free (excl);

	return session;
}

static EnchantSession *
enchant_session_new (EnchantProvider *provider, const char * const lang)
{
	EnchantSession * session = NULL;
	GSList *user_config_dirs, *iter;

	user_config_dirs = enchant_get_user_config_dirs ();
	for (iter = user_config_dirs; iter != NULL && session == NULL; iter = iter->next)
		{
			session =_enchant_session_new (provider, iter->data, lang, TRUE);
		}

	if (session == NULL && user_config_dirs != NULL)
		{
			_enchant_ensure_dir_exists (user_config_dirs->data);
	
			session =_enchant_session_new (provider, user_config_dirs->data, lang, FALSE);
		}

	g_slist_foreach (user_config_dirs, (GFunc)g_free, NULL);
	g_slist_free (user_config_dirs);

	
	return session;
}

static void
enchant_session_add (EnchantSession * session, const char * const word, size_t len)
{
	char* key = g_strndup (word, len);
	g_hash_table_remove (session->session_exclude, key);
	g_hash_table_insert (session->session_include, key, GINT_TO_POINTER(TRUE));
}

static void
enchant_session_remove (EnchantSession * session, const char * const word, size_t len)
{
	char* key = g_strndup (word, len);
	g_hash_table_remove (session->session_include, key);
	g_hash_table_insert (session->session_exclude, key, GINT_TO_POINTER(TRUE));
}

static void
enchant_session_add_personal (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_add(session->personal, word, len);
}

static void
enchant_session_remove_personal (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_remove(session->personal, word, len);
}

static void
enchant_session_add_exclude (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_add(session->exclude, word, len);
}

static void
enchant_session_remove_exclude (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_remove(session->exclude, word, len);
}

/* a word is excluded if it is in the exclude dictionary or in the session exclude list
 *  AND the word has not been added to the session include list
 */
static gboolean
enchant_session_exclude (EnchantSession * session, const char * const word, size_t len)
{
	gboolean result = FALSE;
	
	char * utf = g_strndup (word, len);
	
	if (!g_hash_table_lookup (session->session_include, utf) &&
			(g_hash_table_lookup (session->session_exclude, utf)||
			 enchant_pwl_check (session->exclude, word, len) == 0 ))
			result = TRUE;
	g_free (utf);

	return result;
}

static gboolean
enchant_session_contains (EnchantSession * session, const char * const word, size_t len)
{
	gboolean result = FALSE;
	
	char * utf = g_strndup (word, len);
	
	if (g_hash_table_lookup (session->session_include, utf) ||
		(enchant_pwl_check (session->personal, word, len) == 0 &&
		 !enchant_pwl_check (session->exclude, word, len) == 0))
		result = TRUE;
	
	g_free (utf);

	return result;
}

static void
enchant_session_clear_error (EnchantSession * session)
{
	if (session->error) 
		{
			g_free (session->error);
			session->error = NULL;
		}
}

/********************************************************************************/
/********************************************************************************/

static void
enchant_provider_free_string_list (EnchantProvider * provider, char ** string_list)
{
	if (provider && provider->free_string_list)
		(*provider->free_string_list) (provider, string_list);
}
<commit_msg>Fix a warning from macOS build<commit_after>/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* enchant
 * Copyright (C) 2003, 2004 <NAME>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02110-1301, USA.
 *
 * In addition, as a special exception, <NAME>
 * gives permission to link the code of this program with
 * non-LGPL Spelling Provider libraries (eg: a MSFT Office
 * spell checker backend) and distribute linked combinations including
 * the two.  You must obey the GNU Lesser General Public License in all
 * respects for all of the code used other than said providers.  If you modify
 * this file, you may extend this exception to your version of the
 * file, but you are not obligated to do so.  If you do not wish to
 * do so, delete this exception statement from your version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <glib.h>
#include <gmodule.h>
#include <glib/gstdio.h>
#include <locale.h>

#include "enchant.h"
#include "enchant-provider.h"
#include "pwl.h"

#ifdef XP_TARGET_COCOA
#import "enchant_cocoa.h"
#endif

#ifdef XP_TARGET_COCOA
#define ENCHANT_USER_PATH_EXTENSION "Library", "Application Support", "Enchant"
#elif defined(_WIN32)
#define ENCHANT_USER_PATH_EXTENSION "enchant"
#else
#define ENCHANT_USER_PATH_EXTENSION ".enchant"
#endif

#ifdef ENABLE_BINRELOC
#include "prefix.h"
#endif

ENCHANT_PLUGIN_DECLARE("Enchant")

static char *
enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key);

/********************************************************************************/
/********************************************************************************/

struct str_enchant_broker
{
	GSList *provider_list;	/* list of all of the spelling backend providers */
	GHashTable *dict_map;		/* map of language tag -> dictionary */
	GHashTable *provider_ordering; /* map of language tag -> provider order */
	GHashTable *params;

	gchar * error;
};

typedef struct str_enchant_session
{
	GHashTable *session_include;
	GHashTable *session_exclude;
	EnchantPWL *personal;
	EnchantPWL *exclude;

	char * personal_filename;
	char * exclude_filename;
	char * language_tag;

	char * error;

	gboolean is_pwl;

	EnchantProvider * provider;
} EnchantSession;

typedef struct str_enchant_dict_private_data
{
	unsigned int reference_count;
	EnchantSession* session;
} EnchantDictPrivateData;

typedef EnchantProvider *(*EnchantProviderInitFunc) (void);
typedef void             (*EnchantPreConfigureFunc) (EnchantProvider * provider, const char * module_dir);

/********************************************************************************/
/********************************************************************************/

#ifdef _WIN32
#define path_cmp g_utf8_collate
#else
#define path_cmp strcmp
#endif

static GSList* enchant_slist_prepend_unique_path (GSList *slist, gchar* data)
{
	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
		{
			return g_slist_prepend (slist, data);
		}
	else
		{
			g_free (data);
			return slist;
		}
}

static GSList* enchant_slist_append_unique_path (GSList *slist, gchar* data)
{
	if (NULL == g_slist_find_custom (slist, data, (GCompareFunc)path_cmp))
		{
			return g_slist_append (slist, data);
		}
	else
		{
			g_free (data);
			return slist;
		}
}

static GSList *
_enchant_get_user_home_dirs (void)
{
	GSList *dirs = NULL;
	const char* home_dir;
	char *tmp;

	tmp = enchant_get_registry_value_ex (1, "Config", "Home_Dir");
	if (tmp)
		dirs = enchant_slist_append_unique_path (dirs, tmp);

	home_dir = g_get_home_dir ();
	if (home_dir)
		dirs = enchant_slist_append_unique_path (dirs, g_strdup (home_dir));

	return dirs;
}

static void
_enchant_ensure_dir_exists (const char* dir)
{
	if (dir && !g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) 
		{
			(void)g_remove (dir);
			g_mkdir_with_parents (dir, 0700);                        
		}
}

static GSList *
enchant_get_user_dirs (void)
{
	GSList *user_dirs = NULL;

	{
		const char * user_config_dir;
		
		user_config_dir = g_get_user_config_dir();
		
		if (user_config_dir)
			user_dirs = enchant_slist_append_unique_path (user_dirs, g_build_filename (user_config_dir,
										 "enchant",
										 NULL));
	}

	{
		GSList *home_dirs = NULL, *dir;
		home_dirs = _enchant_get_user_home_dirs ();
		
		for (dir = home_dirs; dir; dir = dir->next)
			{
				user_dirs = enchant_slist_append_unique_path (user_dirs,
							    g_build_filename (dir->data,
									      ENCHANT_USER_PATH_EXTENSION,
									      NULL));
			}

		g_slist_foreach (home_dirs, (GFunc)g_free, NULL);
		g_slist_free (home_dirs);
	}

	return user_dirs;
}

/* place to look for system level providers */
static GSList *
enchant_get_module_dirs (void)
{
	GSList *module_dirs = NULL;

	char * module_dir = NULL;
	char * prefix = NULL;

	{
		char* user_module_dir;
		
		user_module_dir = enchant_get_registry_value_ex (1, "Config", "Module_Dir");
		if (user_module_dir)
			module_dirs = enchant_slist_append_unique_path (module_dirs, user_module_dir);
	}

#ifdef XP_TARGET_COCOA
	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup ([[EnchantResourceProvider instance] moduleFolder]));
#endif

	{
		GSList *user_dirs, *iter;

		user_dirs = enchant_get_user_dirs();

		for (iter = user_dirs; iter; iter = iter->next)
			module_dirs = enchant_slist_append_unique_path (module_dirs, iter->data);
		
		g_slist_free (user_dirs);
	}

	/* Look for explicitly set registry values */
	module_dir = enchant_get_registry_value_ex (0, "Config", "Module_Dir");
	if (module_dir)
		module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);

#if defined(ENCHANT_GLOBAL_MODULE_DIR)
	module_dirs = enchant_slist_append_unique_path (module_dirs, g_strdup (ENCHANT_GLOBAL_MODULE_DIR));
#endif
	/* Dynamically locate library and search for modules relative to it. */
	prefix = enchant_get_prefix_dir();
	if(prefix)
		{
			module_dir = g_build_filename(prefix,"lib","enchant",NULL);
			g_free(prefix);
			module_dirs = enchant_slist_append_unique_path (module_dirs, module_dir);
		}

	return module_dirs;
}

static GSList *
enchant_get_conf_dirs (void)
{
	GSList *conf_dirs = NULL, *user_conf_dirs, *iter;
	char * ordering_dir = NULL, * prefix = NULL;

	user_conf_dirs = enchant_get_user_config_dirs();

	for (iter = user_conf_dirs; iter != NULL; iter = iter->next)
		{
			conf_dirs = enchant_slist_append_unique_path (conf_dirs, iter->data);
		}

	g_slist_free (user_conf_dirs);

#ifdef XP_TARGET_COCOA
	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup ([[EnchantResourceProvider instance] configFolder]));
#endif

	/* Look for explicitly set registry values */
	ordering_dir = enchant_get_registry_value_ex (0, "Config", "Data_Dir");
	if (ordering_dir)
		conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);

	/* Dynamically locate library and search for files relative to it. */
	prefix = enchant_get_prefix_dir();
	if(prefix)
		{
			ordering_dir = g_build_filename(prefix,"share","enchant",NULL);
			g_free(prefix);
			conf_dirs = enchant_slist_append_unique_path (conf_dirs, ordering_dir);
		}

#if defined(ENCHANT_GLOBAL_ORDERING)
	conf_dirs = enchant_slist_append_unique_path (conf_dirs, g_strdup (ENCHANT_GLOBAL_ORDERING));
#endif

	return conf_dirs;
}

ENCHANT_MODULE_EXPORT(FILE *)
enchant_fopen (const gchar *filename, const gchar *mode)
{
#ifdef G_OS_WIN32
  wchar_t *wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
  wchar_t *wmode;
  FILE *retval;
  int save_errno;

  if (wfilename == NULL)
    {
      errno = EINVAL;
      return NULL;
    }

  wmode = g_utf8_to_utf16 (mode, -1, NULL, NULL, NULL);

  if (wmode == NULL)
    {
      g_free (wfilename);
      errno = EINVAL;
      return NULL;
    }

  retval = _wfopen (wfilename, wmode);
  save_errno = errno;

  g_free (wfilename);
  g_free (wmode);

  errno = save_errno;
  return retval;
#else
  return fopen (filename, mode);
#endif
}

/**
 * enchant_get_user_config_dir
 *
 * Returns: the user's enchant directory, or %null. Returned value
 * must be free'd.
 *
 * The enchant directory is the place where enchant finds user 
 * dictionaries and settings related to enchant
 *
 * This API is private to the providers.
 */
ENCHANT_MODULE_EXPORT (GSList *)
enchant_get_user_config_dirs (void)
{
	GSList *dirs;
	char* user_config;

	dirs = enchant_get_user_dirs();

	user_config = enchant_get_registry_value_ex (1, "Config", "Data_Dir");
	if (user_config)
		dirs = enchant_slist_prepend_unique_path (dirs, user_config);

	return dirs;
}

/*
 * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
 */
static char *
enchant_get_registry_value_ex (int current_user, const char * const prefix, const char * const key)
{
#ifndef _WIN32
	/* TODO: GConf? KConfig? */
	return NULL;
#else
	HKEY hKey;
	HKEY baseKey;
	unsigned long lType;	
	DWORD dwSize;
	char* keyName;
	WCHAR* wszValue = NULL;
	char* szValue = NULL;
	gunichar2 * uKeyName;
	gunichar2 * uKey;

	if (current_user)
		baseKey = HKEY_CURRENT_USER;
	else
		baseKey = HKEY_LOCAL_MACHINE;

	keyName = g_strdup_printf("Software\\Enchant\\%s", prefix);
	uKeyName = g_utf8_to_utf16 (keyName, -1, NULL, NULL, NULL);
	uKey = g_utf8_to_utf16 (key, -1, NULL, NULL, NULL);

	if(RegOpenKeyExW(baseKey, uKeyName, 0, KEY_READ, &hKey) == ERROR_SUCCESS)
		{
			/* Determine size of string */
			if(RegQueryValueExW( hKey, uKey, NULL, &lType, NULL, &dwSize) == ERROR_SUCCESS)
				{
					wszValue = g_new0(WCHAR, dwSize + 1);
					RegQueryValueExW(hKey, uKey, NULL, &lType, (LPBYTE) wszValue, &dwSize);
				}
			RegCloseKey(hKey);
		}

	if(wszValue && *wszValue)
		szValue = g_utf16_to_utf8 (wszValue, -1, NULL, NULL, NULL);

	g_free(keyName);
	g_free(uKeyName);
	g_free(uKey);
	g_free(wszValue);

	return szValue;
#endif
}

/**
 * enchant_get_registry_value
 * @prefix: Your category, such as "Ispell" or "Myspell"
 * @key: The tag within your category that you're interested in
 *
 * Returns: the value if it exists and is not an empty string ("") or %null otherwise. Must be free'd.
 *
 * This API is private to the providers.
 */
ENCHANT_MODULE_EXPORT (char *)
enchant_get_registry_value (const char * const prefix, const char * const key)
{
	char *val;

	g_return_val_if_fail (prefix, NULL);
	g_return_val_if_fail (key, NULL);

	val = enchant_get_registry_value_ex(1, prefix, key);
	if(val == NULL) {
			val = enchant_get_registry_value_ex (0, prefix, key);
		}
	return val;
}

/********************************************************************************/
/********************************************************************************/

static gchar*
enchant_modify_string_chars (gchar *str,
							 gssize len,
							 gchar (*function)(gchar))
{
	gchar* it, *end;

	g_return_val_if_fail (str != NULL, NULL);

	if (len < 0)
		len = strlen (str);

	end = str + len;

	for (it = str; it != end; ++it)
		*it = function (*it);

	return str;
}

static gchar*
enchant_ascii_strup (gchar *str,
					 gssize len)
{
	return enchant_modify_string_chars(str, len, g_ascii_toupper);
}

static gchar*
enchant_ascii_strdown (gchar *str,
						  gssize len)
{
	return enchant_modify_string_chars(str, len, g_ascii_tolower);
}

/* returns TRUE if tag is valid
 * for requires alphanumeric ASCII or underscore
 */
static int
enchant_is_valid_dictionary_tag(const char * const tag)
{
	const char * it;
	for (it = tag; *it; ++it)
		{
			if(!g_ascii_isalnum(*it) && *it != '_')
				return 0;
		}

	return it != tag; /*empty tag invalid*/
}

static char *
enchant_normalize_dictionary_tag (const char * const dict_tag)
{
	char * new_tag = g_strdup (dict_tag);
	char * needle;

	new_tag = g_strstrip (new_tag);

	/* strip off en_GB@euro */
	if ((needle = strchr (new_tag, '@')) != NULL)
		*needle = '\0';

	/* strip off en_GB.UTF-8 */
	if ((needle = strchr (new_tag, '.')) != NULL)
		*needle = '\0';

	/* turn en-GB into en_GB */
	if ((needle = strchr (new_tag, '-')) != NULL)
		*needle = '_';

	/* everything before first '_' is converted to lower case */
	if ((needle = strchr (new_tag, '_')) != NULL) {
			enchant_ascii_strdown(new_tag, needle - new_tag);
			++needle;
			/* everything after first '_' is converted to upper case */
			enchant_ascii_strup(needle, -1);
		}
	else {
			enchant_ascii_strdown(new_tag, -1);
		}

	return new_tag;
}

static char *
enchant_iso_639_from_tag (const char * const dict_tag)
{
	char * new_tag = g_strdup (dict_tag);
	char * needle;

	if ((needle = strchr (new_tag, '_')) != NULL)
		*needle = '\0';

	return new_tag;
}

static void
enchant_session_destroy (EnchantSession * session)
{
	g_hash_table_destroy (session->session_include);
	g_hash_table_destroy (session->session_exclude);
	enchant_pwl_free (session->personal);
	enchant_pwl_free (session->exclude);
	g_free (session->personal_filename);
	g_free (session->exclude_filename);
	g_free (session->language_tag);

	if (session->error)
		g_free (session->error);

	g_free (session);
}

static EnchantSession *
enchant_session_new_with_pwl (EnchantProvider * provider, 
							  const char * const pwl, 
							  const char * const excl,
							  const char * const lang,
							  gboolean fail_if_no_pwl)
{
	EnchantSession * session;
	EnchantPWL *personal = NULL;
	EnchantPWL *exclude = NULL;

	if (pwl)
		personal = enchant_pwl_init_with_file (pwl);

	if (personal == NULL) {
		if (fail_if_no_pwl)
			return NULL;
		else
			personal = enchant_pwl_init ();
	}
	
	if (excl)
		exclude = enchant_pwl_init_with_file (excl);
	if (exclude == NULL)
		exclude = enchant_pwl_init ();

	session = g_new0 (EnchantSession, 1);
	session->session_include = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	session->session_exclude = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
	session->personal = personal;
	session->exclude = exclude;
	session->provider = provider;
	session->language_tag = g_strdup (lang);
	session->personal_filename = g_strdup (pwl);
	session->exclude_filename = g_strdup (excl);
	
	return session;
}

static EnchantSession *
_enchant_session_new (EnchantProvider *provider, const char * const user_config_dir, 
		      const char * const lang, gboolean fail_if_no_pwl)
{
	char *filename, *dic, *excl;
	EnchantSession * session;

	if (!user_config_dir || !lang)
		return NULL;
	
	filename = g_strdup_printf ("%s.dic", lang);
	dic = g_build_filename (user_config_dir, filename, NULL);
	g_free (filename);
	
	filename = g_strdup_printf ("%s.exc", lang);
	excl = g_build_filename (user_config_dir, filename,	NULL);
	g_free (filename);
	
	session = enchant_session_new_with_pwl (provider, dic, excl, lang, fail_if_no_pwl);
	
	g_free (dic);
	g_free (excl);

	return session;
}

static EnchantSession *
enchant_session_new (EnchantProvider *provider, const char * const lang)
{
	EnchantSession * session = NULL;
	GSList *user_config_dirs, *iter;

	user_config_dirs = enchant_get_user_config_dirs ();
	for (iter = user_config_dirs; iter != NULL && session == NULL; iter = iter->next)
		{
			session =_enchant_session_new (provider, iter->data, lang, TRUE);
		}

	if (session == NULL && user_config_dirs != NULL)
		{
			_enchant_ensure_dir_exists (user_config_dirs->data);
	
			session =_enchant_session_new (provider, user_config_dirs->data, lang, FALSE);
		}

	g_slist_foreach (user_config_dirs, (GFunc)g_free, NULL);
	g_slist_free (user_config_dirs);

	
	return session;
}

static void
enchant_session_add (EnchantSession * session, const char * const word, size_t len)
{
	char* key = g_strndup (word, len);
	g_hash_table_remove (session->session_exclude, key);
	g_hash_table_insert (session->session_include, key, GINT_TO_POINTER(TRUE));
}

static void
enchant_session_remove (EnchantSession * session, const char * const word, size_t len)
{
	char* key = g_strndup (word, len);
	g_hash_table_remove (session->session_include, key);
	g_hash_table_insert (session->session_exclude, key, GINT_TO_POINTER(TRUE));
}

static void
enchant_session_add_personal (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_add(session->personal, word, len);
}

static void
enchant_session_remove_personal (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_remove(session->personal, word, len);
}

static void
enchant_session_add_exclude (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_add(session->exclude, word, len);
}

static void
enchant_session_remove_exclude (EnchantSession * session, const char * const word, size_t len)
{
	enchant_pwl_remove(session->exclude, word, len);
}

/* a word is excluded if it is in the exclude dictionary or in the session exclude list
 *  AND the word has not been added to the session include list
 */
static gboolean
enchant_session_exclude (EnchantSession * session, const char * const word, size_t len)
{
	gboolean result = FALSE;
	
	char * utf = g_strndup (word, len);
	
	if (!g_hash_table_lookup (session->session_include, utf) &&
			(g_hash_table_lookup (session->session_exclude, utf)||
			 enchant_pwl_check (session->exclude, word, len) == 0 ))
			result = TRUE;
	g_free (utf);

	return result;
}

static gboolean
enchant_session_contains (EnchantSession * session, const char * const word, size_t len)
{
	gboolean result = FALSE;
	
	char * utf = g_strndup (word, len);
	
	if (g_hash_table_lookup (session->session_include, utf) ||
		(enchant_pwl_check (session->personal, word, len) == 0 &&
		 (!enchant_pwl_check (session->exclude, word, len)) == 0))
		result = TRUE;
	
	g_free (utf);

	return result;
}

static void
enchant_session_clear_error (EnchantSession * session)
{
	if (session->error) 
		{
			g_free (session->error);
			session->error = NULL;
		}
}

/********************************************************************************/
/********************************************************************************/

static void
enchant_provider_free_string_list (EnchantProvider * provider, char ** string_list)
{
	if (provider && provider->free_string_list)
		(*provider->free_string_list) (provider, string_list);
}

{'fraction_non_alphanumeric': 0.1398921044938744, 'fraction_numerical': 0.004677243615296712, 'mean_word_length': 4.6928895612708015, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 119, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5645314', 'n_tokens_mistral': 14173, 'n_tokens_neox': 13122, 'n_words': 3399}