Blob Blame History Raw
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* 
 * ezfc-config.c
 * Copyright (C) 2011-2015 Akira TAGOH
 * 
 * Authors:
 *   Akira TAGOH  <akira@tagoh.org>
 * 
 * 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 3 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <errno.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libeasyfc/ezfc-error.h>
#include <libeasyfc/ezfc-font.h>
#include "ezfc-font-private.h"
#include <libeasyfc/ezfc-utils.h>
#include "ezfc-mem.h"
#include <libeasyfc/ezfc-config.h>

/**
 * SECTION:ezfc-config
 * @Short_Description: A class for managing the configuration file.
 * @Title: ezfc_config_t
 *
 * This class provides an easy access to assign an instance of #ezfc_alias_t
 * to a language and to read/write the configuration file.
 */

struct _ezfc_config_t {
	ezfc_mem_t  parent;
	GHashTable *aliases;
	GHashTable *fonts;
	GHashTable *subst;
	gchar      *name;
	gint        priority;
	gboolean    loaded;
	gboolean    migration;
};
typedef struct _ezfc_container_t {
	ezfc_mem_t          parent;
	gpointer            data;
	ezfc_destroy_func_t data_finalizer;
} ezfc_container_t;
typedef struct _ezfc_xml_const_t {
	gchar *name;
	gint   val;
} ezfc_xml_const_t;
typedef enum {
	EZFC_NODE_TYPE_PREFER = 0,
	EZFC_NODE_TYPE_ACCEPT,
	EZFC_NODE_TYPE_DEFAULT,
	EZFC_NODE_TYPE_END
} ezfc_node_t;

/*< private >*/
static void
_ezfc_config_alias_list_free(gpointer p)
{
	GList *l = p, *ll;

	for (ll = l; ll != NULL; ll = g_list_next(ll)) {
		ezfc_alias_unref(ll->data);
	}
	g_list_free(l);
}

static void
_ezfc_config_subst_list_free(gpointer p)
{
	GList *l = p, *ll;

	for (ll = l; ll != NULL; ll = g_list_next(ll)) {
		ezfc_font_unref(ll->data);
	}
	g_list_free(l);
}

static ezfc_container_t *
ezfc_container_new(ezfc_destroy_func_t func)
{
	ezfc_container_t *retval = ezfc_mem_alloc_object(sizeof (ezfc_container_t));

	if (retval) {
		retval->data_finalizer = func;
	}

	return retval;
}

#if 0
static ezfc_container_t *
ezfc_container_ref(ezfc_container_t *container)
{
	g_return_val_if_fail (container != NULL, NULL);

	return ezfc_mem_ref(&container->parent);
}
#endif

static void
ezfc_container_unref(ezfc_container_t *container)
{
	if (container)
		ezfc_mem_unref(&container->parent);
}

static void
ezfc_container_set(ezfc_container_t *container,
		   gpointer          p)
{
	g_return_if_fail (container != NULL);

	if (container->data)
		ezfc_mem_delete_ref(&container->parent, container->data);
	container->data = p;
	ezfc_mem_add_ref(&container->parent, container->data,
			 container->data_finalizer);
}

static gpointer
ezfc_container_get(const ezfc_container_t *container)
{
	g_return_val_if_fail (container != NULL, NULL);

	return container->data;
}

static xmlNodePtr
_ezfc_config_to_xml_node(const gchar    *language,
			 const gchar    *test_font,
			 const gpointer  edit_object,
			 ezfc_node_t     type)
{
	xmlNodePtr match, test, edit, comment;
	FcPattern *pat;
	FcChar8 *family;
	const gchar *mode[] = {
		"prepend",
		"append",
		"append_last",
		NULL
	};

	g_return_val_if_fail (type < EZFC_NODE_TYPE_END, NULL);

	match = xmlNewNode(NULL,
			   (const xmlChar *)"match");
	xmlNewProp(match,
		   (const xmlChar *)"target",
		   (const xmlChar *)"pattern");

	if (language && language[0] != 0) {
		FcChar8 *normlang = FcLangNormalize((const FcChar8 *)language);
		gchar *s = g_strdup_printf("lang=%s", language);

		comment = xmlNewComment((const xmlChar *)s);
		g_free(s);
		test = xmlNewChild(match, NULL,
				   (const xmlChar *)"test",
				   NULL);
		xmlNewProp(test,
			   (const xmlChar *)"name",
			   (const xmlChar *)FC_LANG);
		xmlAddChild(test, comment);
		xmlNewChild(test, NULL,
			    (const xmlChar *)"string",
			    (const xmlChar *)normlang);
		FcStrFree(normlang);
	}

	test = xmlNewChild(match, NULL,
			   (const xmlChar *)"test",
			   NULL);
	xmlNewProp(test,
		   (const xmlChar *)"name",
		   (const xmlChar *)FC_FAMILY);
	xmlNewChild(test, NULL,
		    (const xmlChar *)"string",
		    (const xmlChar *)test_font);

	if (type == EZFC_NODE_TYPE_DEFAULT) {
		family = edit_object;
	} else {
		pat = edit_object;
		/* XXX: there are no way to peek everything in FcPattern efficiently.
		   so only support family name so far.
		*/
		if (FcPatternGetString(pat, FC_FAMILY, 0, &family) != FcResultMatch)
			return NULL;
	}
	edit = xmlNewChild(match, NULL,
			   (const xmlChar *)"edit",
			   NULL);
	xmlNewProp(edit,
		   (const xmlChar *)"name",
		   (const xmlChar *)FC_FAMILY);
	xmlNewProp(edit,
		   (const xmlChar *)"mode",
		   (const xmlChar *)mode[type]);
	xmlNewChild(edit, NULL,
		    (const xmlChar *)"string",
		    (const xmlChar *)family);

	return match;
}

static gboolean
_ezfc_config_real_to_xml(xmlNodePtr              root,
			 const gchar            *slang,
			 const ezfc_container_t *container,
			 gboolean               *no_elements)
{
	const GList *list, *l;
	xmlNodePtr node;

	list = ezfc_container_get(container);
	for (l = list; l != NULL; l = g_list_next(l)) {
		ezfc_alias_t *a = l->data;
		const gchar *n;
		gboolean is_subst;
		FcPattern *pat;
		FcChar8 *family;

		n = ezfc_alias_get_name(a);
		pat = ezfc_alias_get_font_pattern(a);
		is_subst = !ezfc_font_is_alias_font(n);
		if (is_subst) {
			node = _ezfc_config_to_xml_node(slang,
							n, pat,
							EZFC_NODE_TYPE_ACCEPT);
			xmlAddChild(root, node);
		} else {
			if (FcPatternGetString(pat, FC_FAMILY, 0, &family) != FcResultMatch)
				return FALSE;
			node = _ezfc_config_to_xml_node(slang,
							(const gchar *)family,
							(const gpointer)n,
							EZFC_NODE_TYPE_DEFAULT);
			xmlAddChild(root, node);
			node = _ezfc_config_to_xml_node(slang,
							n, pat,
							EZFC_NODE_TYPE_PREFER);
			xmlAddChild(root, node);
		}
		*no_elements = FALSE;
		FcPatternDestroy(pat);
	}

	return TRUE;
}

static gboolean
_ezfc_config_real_to_font_xml(xmlNodePtr   root,
			      ezfc_font_t *font,
			      gboolean    *no_elements)
{
	xmlNodePtr match, test, edit;
	const gchar *family = ezfc_font_get_family(font);
	gint masks, n = 0;
	GList *feat = ezfc_font_get_features(font);

	masks = ezfc_font_get_boolean_masks(font);
	if (masks == 0 && !feat)
		return TRUE;
	match = xmlNewNode(NULL,
			   (const xmlChar *)"match");
	xmlNewProp(match,
		   (const xmlChar *)"target",
		   (const xmlChar *)"font");
	test = xmlNewChild(match, NULL,
			   (const xmlChar *)"test",
			   NULL);
	xmlNewProp(test,
		   (const xmlChar *)"name",
		   (const xmlChar *)FC_FAMILY);
	xmlNewChild(test, NULL,
		    (const xmlChar *)"string",
		    (const xmlChar *)family);
	while (masks != 0) {
		gint i = (masks & 1) << n;
		gboolean f;
		static const gchar *hintstyles[] = {
			NULL,
			"hintnone",
			"hintslight",
			"hintmedium",
			"hintfull",
			NULL
		};
		static const gchar *rgba[] = {
			"unknown",
			"rgb",
			"bgr",
			"vrgb",
			"vbgr",
			"none",
			NULL
		};

		if (i == 0 || i >= EZFC_FONT_MASK_END)
			goto bail;
		edit = xmlNewChild(match, NULL,
				   (const xmlChar *)"edit",
				   NULL);
		switch (i) {
		    case EZFC_FONT_MASK_HINTING:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_HINTING);
			    f = ezfc_font_get_hinting(font);
			    break;
		    case EZFC_FONT_MASK_AUTOHINT:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_AUTOHINT);
			    f = ezfc_font_get_autohinting(font);
			    break;
		    case EZFC_FONT_MASK_ANTIALIAS:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_ANTIALIAS);
			    f = ezfc_font_get_antialiasing(font);
			    break;
		    case EZFC_FONT_MASK_EMBEDDED_BITMAP:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_EMBEDDED_BITMAP);
			    f = ezfc_font_get_embedded_bitmap(font);
			    break;
		    case EZFC_FONT_MASK_RGBA:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_RGBA);
			    xmlNewProp(edit,
				       (const xmlChar *)"mode",
				       (const xmlChar *)"assign");
			    xmlNewChild(edit, NULL,
					(const xmlChar *)"const",
					(const xmlChar *)rgba[ezfc_font_get_rgba(font)]);
			    goto bail;
		    case EZFC_FONT_MASK_HINTSTYLE:
			    xmlNewProp(edit,
				       (const xmlChar *)"name",
				       (const xmlChar *)FC_HINT_STYLE);
			    xmlNewProp(edit,
				       (const xmlChar *)"mode",
				       (const xmlChar *)"assign");
			    xmlNewChild(edit, NULL,
					(const xmlChar *)"const",
					(const xmlChar *)hintstyles[ezfc_font_get_hintstyle(font)]);
			    goto bail;
		    default:
			    g_return_val_if_reached(FALSE);
		}
		xmlNewProp(edit,
			   (const xmlChar *)"mode",
			   (const xmlChar *)"assign");
		xmlNewChild(edit, NULL,
			    (const xmlChar *)"bool",
			    (const xmlChar *)(f ? "true" : "false"));
	  bail:
		masks >>= 1;
		n++;
	}
	if (feat) {
		GList *l;

		edit = xmlNewChild(match, NULL,
				   (const xmlChar *)"edit",
				   NULL);
		xmlNewProp(edit,
			   (const xmlChar *)"name",
			   (const xmlChar *)FC_FONT_FEATURES);
		xmlNewProp(edit,
			   (const xmlChar *)"mode",
			   (const xmlChar *)"append");
		for (l = feat; l != NULL; l = g_list_next(l)) {
			const xmlChar *n = l->data;

			xmlNewChild(edit, NULL,
				    (const xmlChar *)"string",
				    n);
		}
		g_list_free(feat);
	}
	xmlAddChild(root, match);
	*no_elements = FALSE;

	return TRUE;
}

static gboolean
_ezfc_config_real_to_subst_xml(xmlNodePtr              root,
			       const gchar            *family_name,
			       const ezfc_container_t *container,
			       gboolean               *no_elements)
{
	const GList *list, *l;
	xmlNodePtr match, test, edit;

	match = xmlNewNode(NULL,
			   (const xmlChar *)"match");
	xmlNewProp(match,
		   (const xmlChar *)"target",
		   (const xmlChar *)"pattern");
	test = xmlNewChild(match, NULL,
			   (const xmlChar *)"test",
			   NULL);
	xmlNewProp(test,
		   (const xmlChar *)"name",
		   (const xmlChar *)FC_FAMILY);
	xmlNewChild(test, NULL,
		    (const xmlChar *)"string",
		    (const xmlChar *)family_name);
	edit = xmlNewChild(match, NULL,
			   (const xmlChar *)"edit",
			   NULL);
	xmlNewProp(edit,
		   (const xmlChar *)"name",
		   (const xmlChar *)FC_FAMILY);
	xmlNewProp(edit,
		   (const xmlChar *)"mode",
		   (const xmlChar *)"append_last");
	xmlNewProp(edit,
		   (const xmlChar *)"binding",
		   (const xmlChar *)"same");
	list = ezfc_container_get(container);
	for (l = list; l != NULL; l = g_list_next(l)) {
		ezfc_font_t *f = l->data;
		FcPattern *pat;
		FcChar8 *n;
		int i;

		pat = ezfc_font_get_pattern(f);
		for (i = 0; ; i++) {
			if (FcPatternGetString(pat, FC_FAMILY, i, &n) != FcResultMatch)
				break;
			xmlNewChild(edit, NULL,
				    (const xmlChar *)"string",
				    (const xmlChar *)n);
		}
		FcPatternDestroy(pat);
	}
	xmlAddChild(root, match);
	*no_elements = FALSE;

	return TRUE;
}

static gboolean
_ezfc_config_to_xml(ezfc_config_t  *config,
		    xmlChar       **output,
		    int            *size)
{
	xmlDocPtr doc;
	xmlNodePtr root;
	gchar *comment;
	GHashTableIter iter;
	gboolean no_elements = TRUE;
	gpointer key, val;

	doc = xmlNewDoc((const xmlChar *)"1.0");
	doc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
	xmlCreateIntSubset(doc,
			   (const xmlChar *)"fontconfig",
			   NULL,
			   (const xmlChar *)"../fonts.dtd");
	root = xmlNewDocNode(doc,
			     NULL,
			     (const xmlChar *)"fontconfig",
			     NULL);
	xmlDocSetRootElement(doc, root);
	comment = g_strdup_printf("\n"
				  "\tTHIS FILE WAS GENERATED BY libeasyfc %s\n"
				  "\tDO NOT EDIT THIS DIRECTLY\n"
				  "\tANY CHANGES HAS BEEN MADE MANUALLY MAY BE LOST.\n",
				  VERSION);
	xmlAddChild(root, xmlNewComment((const xmlChar *)comment));
	g_free(comment);

	g_hash_table_iter_init(&iter, config->aliases);
	while (g_hash_table_iter_next(&iter, &key, &val)) {
		const gchar *slang = key;
		const ezfc_container_t *container = val;

		if (!slang || slang[0] == 0) {
			/* "any" language should be added at the end. */
			continue;
		}
		if (!_ezfc_config_real_to_xml(root, slang, container, &no_elements))
			return FALSE;
	}
	val = g_hash_table_lookup(config->aliases, "");
	if (val)
		if (!_ezfc_config_real_to_xml(root, NULL, val, &no_elements))
			return FALSE;
	g_hash_table_iter_init(&iter, config->fonts);
	while (g_hash_table_iter_next(&iter, &key, &val)) {
		ezfc_font_t *font = val;

		if (!_ezfc_config_real_to_font_xml(root, font, &no_elements))
			return FALSE;
	}
	g_hash_table_iter_init(&iter, config->subst);
	while (g_hash_table_iter_next(&iter, &key, &val)) {
		const gchar *n = key;
		const ezfc_container_t *container = val;

		if (!_ezfc_config_real_to_subst_xml(root, n, container, &no_elements))
			return FALSE;
	}

	xmlDocDumpFormatMemory(doc, output, size, 1);

	xmlFreeDoc(doc);

	return !no_elements;
}

static GList *
_ezfc_config_parse_comment_node(xmlNodePtr node)
{
	xmlChar *s = NULL;
	GList *retval = NULL;

	while (node != NULL) {
		if (node->type == XML_COMMENT_NODE) {
			s = xmlNodeGetContent(node);
			retval = g_list_append(retval, s);
		}
		node = node->next;
	}

	return retval;
}

static GList *
_ezfc_config_parse_string_node(xmlNodePtr node)
{
	xmlChar *s = NULL;
	GList *retval = NULL;

	while (node != NULL) {
		if (node->type == XML_COMMENT_NODE || node->type == XML_TEXT_NODE);
		else if (node->type == XML_ELEMENT_NODE &&
		    xmlStrcmp(node->name, (const xmlChar *)"string") == 0) {
			s = xmlNodeGetContent(node);
			retval = g_list_append(retval, s);
		} else {
			g_warning("Unexpected node: type = %d, name = %s",
				  node->type, node->name);
			break;
		}
		node = node->next;
	}

	return retval;
}

static gboolean
_ezfc_config_parse_bool_node(xmlNodePtr node)
{
	gboolean retval = FALSE;
	xmlChar *s;
	gchar *ss;

	while (node != NULL) {
		if (node->type == XML_COMMENT_NODE || node->type == XML_TEXT_NODE)
			node = node->next;
		if (node->type == XML_ELEMENT_NODE &&
		    xmlStrcmp(node->name, (const xmlChar *)"bool") == 0) {
			s = xmlNodeGetContent(node);
			ss = g_ascii_strdown((const gchar *)s, -1);
			xmlFree(s);
			if (ss[0] == 't' || ss[0] == 'y' || ss[0] == '1')
				retval = TRUE;
			break;
		} else {
			g_warning("Unexpected node: type = %d, name = %s",
				  node->type, node->name);
			break;
		}
	}

	return retval;
}

static gboolean
_ezfc_config_parse_const_node(xmlNodePtr        node,
			      ezfc_xml_const_t *list,
			      gint             *ret)
{
	gboolean retval = FALSE;
	xmlChar *s;
	ezfc_xml_const_t *c;

	while (node != NULL) {
		if (node->type == XML_COMMENT_NODE || node->type == XML_TEXT_NODE)
			node = node->next;
		if (node->type == XML_ELEMENT_NODE &&
		    xmlStrcmp(node->name, (const xmlChar *)"const") == 0) {
			s = xmlNodeGetContent(node);
			for (c = list; c != NULL; c++) {
				if (g_ascii_strcasecmp(c->name, (const gchar *)s) == 0) {
					*ret = c->val;
					retval = TRUE;
					break;
				}
			}
			xmlFree(s);
			break;
		} else {
			g_warning("Unexpected node: type = %d, name = %s",
				  node->type, node->name);
			break;
		}
	}

	return retval;
}

static gboolean
_ezfc_config_from_xml(ezfc_config_t  *config,
		      const gchar    *conffile,
		      GError        **error)
{
	xmlDocPtr doc = NULL;
	xmlXPathContextPtr xctxt = NULL;
	xmlXPathObjectPtr xobj = NULL;
	gboolean retval = TRUE;
	GError *err = NULL;
	int i, n;

	xmlInitParser();

	doc = xmlParseFile(conffile);
	if (!doc) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_XML,
			    "Unable to read the configuration.");
		retval = FALSE;
		goto bail;
	}
	xctxt = xmlXPathNewContext(doc);
	if (!xctxt) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_XML,
			    "Unable to create a XPath context.");
		retval = FALSE;
		goto bail;
	}
	xobj = xmlXPathEvalExpression((const xmlChar *)"/fontconfig/match[(@target = \"pattern\" or not(@*)) and (test[@name = \"family\"] and test/string/text()) and (edit[@name = \"family\"] and edit/string/text())]", xctxt);
	if (!xobj) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_XML,
			    "No valid elements can be proceeded in this library.");
		retval = FALSE;
		goto bail;
	}

	n = xmlXPathNodeSetGetLength(xobj->nodesetval);

	for (i = 0; i < n; i++) {
		xmlNodePtr match = xmlXPathNodeSetItem(xobj->nodesetval, i);
		xmlNodePtr node;
		xmlChar *attr, *lang = NULL, *alias = NULL;
		FcPattern *pat = FcPatternCreate();
		ezfc_alias_t *a;
		ezfc_font_t *f;
		gboolean is_subst = FALSE;
		GList *l, *ll, *cl, *cll;

		node = match->children;
		while (node != NULL) {
			if (node->type == XML_TEXT_NODE) {
				/* ignore the text node here */
				goto bail1;
			}
			if (xmlStrcmp(node->name, (const xmlChar *)"test") == 0) {
				attr = xmlGetProp(node, (const xmlChar *)"name");
				if (xmlStrcmp(attr, (const xmlChar *)FC_LANG) == 0) {
					if (lang)
						xmlFree(lang);
					l = _ezfc_config_parse_string_node(node->children);
					cl = _ezfc_config_parse_comment_node(node->children);
					/* multiple node is not well supported */
					lang = xmlStrdup(l->data);
					for (cll = cl; cll; cll = g_list_next(cll)) {
						if (xmlStrncmp((const xmlChar *)cll->data, (const xmlChar *)"lang=", 5) == 0) {
							gchar *data = cll->data;
							gchar *clang = g_strdup(&data[5]);
							FcChar8 *normlang;

							if (clang && clang[0]) {
								normlang = FcLangNormalize((const FcChar8 *)clang);
								if (xmlStrcmp(lang, (const xmlChar *)normlang) == 0) {
									xmlFree(lang);
									lang = xmlStrdup((const xmlChar *)clang);
								}
								FcStrFree(normlang);
							}
							if (clang)
								g_free(clang);
						}
					}
					g_list_free_full(cl, xmlFree);
					g_list_free_full(l, xmlFree);
				} else if (xmlStrcmp(attr, (const xmlChar *)FC_FAMILY) == 0) {
					if (alias)
						xmlFree(alias);
					l = _ezfc_config_parse_string_node(node->children);
					/* multiple node is not well supported */
					alias = xmlStrdup(l->data);
					g_list_free_full(l, xmlFree);
				} else {
					g_warning("Unexpected value in the name attribute on <test>: %s\n",
						  attr);
				}
				xmlFree(attr);
			} else if (xmlStrcmp(node->name, (const xmlChar *)"edit") == 0) {
				attr = xmlGetProp(node, (const xmlChar *)"mode");
				if (xmlStrcmp(attr, (const xmlChar *)"append_last") == 0) {
					/* kind of the alternatives of <default> */
					/* We are assuming that there should be the <prefer> rule too.
					 * so ignoring except if binding is "same".
					 */
					xmlFree(attr);
					attr = xmlGetProp(node, (const xmlChar *)"binding");
					if (!attr || xmlStrcmp(attr, (const xmlChar *)"same"))
					{
						if (attr)
							xmlFree(attr);
						goto bail2;
					}
					/* deal with this as the substitute font definition */
					xmlFree(attr);
					attr = xmlGetProp(node, (const xmlChar *)"name");
					if (xmlStrcmp(attr, (const xmlChar *)FC_FAMILY) == 0) {
						l = _ezfc_config_parse_string_node(node->children);
						for (ll = l; ll != NULL; ll = g_list_next(ll))
							FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)ll->data);
						g_list_free_full(l, xmlFree);
					} else {
						g_warning("Unexpected value in the name attribute: %s",
							  attr);
					}
					is_subst = TRUE;
				} else {
					xmlFree(attr);
					attr = xmlGetProp(node, (const xmlChar *)"name");
					if (xmlStrcmp(attr, (const xmlChar *)FC_FAMILY) == 0) {
						l = _ezfc_config_parse_string_node(node->children);
						for (ll = l; ll != NULL; ll = g_list_next(ll))
							FcPatternAddString(pat, FC_FAMILY, (const FcChar8 *)ll->data);
						g_list_free_full(l, xmlFree);
					} else {
						g_warning("Unexpected value in the name attribute: %s",
							  attr);
					}
				}
				xmlFree(attr);
			} else {
				g_warning("Unexpected element in <match>: %s\n", node->name);
			}
		  bail1:
			node = node->next;
		}
		if (!lang)
			lang = xmlStrdup((const xmlChar *)"");
		if (!alias) {
			g_warning("No alias name is defined.");
			goto bail2;
		}
		if (is_subst) {
			f = ezfc_font_new();
			if (!ezfc_font_set_pattern(f, pat, &err)) {
				ezfc_font_unref(f);
				goto bail2;
			}
			l = ezfc_font_canonicalize(f, &err);
			if (!l) {
				ezfc_font_unref(f);
				goto bail2;
			}
			for (ll = l; ll != NULL; ll = g_list_next(ll)) {
				ezfc_font_t *ff = ll->data;

				ezfc_config_add_subst(config, (const gchar *)alias, ff);
			}
			ezfc_font_unref(f);
			g_list_free_full(l, (GDestroyNotify)ezfc_font_unref);
		} else {
			a = ezfc_alias_new((const gchar *)alias);
			if (!ezfc_alias_set_font_pattern(a, pat, &err)) {
				ezfc_alias_unref(a);
				goto bail2;
			}
			ezfc_config_add_alias(config, (const gchar *)lang, a);
			ezfc_alias_unref(a);
		}
	  bail2:
		if (lang)
			xmlFree(lang);
		if (alias)
			xmlFree(alias);
		if (pat)
			FcPatternDestroy(pat);
		if (err)
			break;
	}

	xobj = xmlXPathEvalExpression((const xmlChar *)"/fontconfig/match[@target = \"font\" and (test[@name = \"family\"] and test/string/text())]", xctxt);
	if (!xobj) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_XML,
			    "No valid elements for targeting fonts can be proceeded in this library.");
		retval = FALSE;
		goto bail;
	}

	n = xmlXPathNodeSetGetLength(xobj->nodesetval);

	for (i = 0; i < n; i++) {
		xmlNodePtr match = xmlXPathNodeSetItem(xobj->nodesetval, i);
		xmlNodePtr node;
		xmlChar *attr;
		ezfc_font_t *f = NULL;
		GList *l, *ll;

		node = match->children;
		while (node != NULL) {
			if (node->type == XML_TEXT_NODE) {
				/* ignore the text node here */
				goto bail3;
			}
			if (xmlStrcmp(node->name, (const xmlChar *)"test") == 0) {
				attr = xmlGetProp(node, (const xmlChar *)"name");
				if (xmlStrcmp(attr, (const xmlChar *)FC_FAMILY) == 0) {
					gboolean b;

					f = ezfc_font_new();
					l = _ezfc_config_parse_string_node(node->children);
					/* multiple values is not well supported */
					/* ezfc_font_add_family may fails if a family doesn't exist in the system
					 * this is just an exception not to check the existence of fonts
					 * so that we can simply ignore that invalid entry until
					 * one installs the missing fonts again.
					 */
					b = ezfc_font_get_check_existence(f);
					ezfc_font_check_existence(f, FALSE);
					ezfc_font_add_family(f, (const gchar *)l->data, &err);
					ezfc_font_check_existence(f, b);
					g_list_free_full(l, xmlFree);
				}
				xmlFree(attr);
			} else if (xmlStrcmp(node->name, (const xmlChar *)"edit") == 0) {
				attr = xmlGetProp(node, (const xmlChar *)"mode");
				if (xmlStrcmp(attr, (const xmlChar *)"append") == 0) {
					xmlFree(attr);
					attr = xmlGetProp(node, (const xmlChar *)"name");
					if (xmlStrcmp(attr, (const xmlChar *)FC_FONT_FEATURES) == 0) {
						l = _ezfc_config_parse_string_node(node->children);
						for (ll = l; ll != NULL; ll = g_list_next(ll)) {
							ezfc_font_add_feature(f, (const gchar *)ll->data);
						}
						g_list_free(l);
					} else {
						g_warning("Unexpected value in the name attribute: %s",
							  attr);
					}
					xmlFree(attr);
				} else if (xmlStrcmp(attr, (const xmlChar *)"assign") != 0) {
					g_warning("Unexpected mode for <match target=\"font\"><edit>: %s",
						  attr);
					xmlFree(attr);
					ezfc_font_unref(f);
					goto bail4;
				} else {
					xmlFree(attr);
					attr = xmlGetProp(node, (const xmlChar *)"name");
					if (xmlStrcmp(attr, (const xmlChar *)FC_ANTIALIAS) == 0) {
						ezfc_font_set_antialiasing(f, _ezfc_config_parse_bool_node(node->children));
					} else if (xmlStrcmp(attr, (const xmlChar *)FC_AUTOHINT) == 0) {
						ezfc_font_set_autohinting(f, _ezfc_config_parse_bool_node(node->children));
					} else if (xmlStrcmp(attr, (const xmlChar *)FC_EMBEDDED_BITMAP) == 0) {
						ezfc_font_set_embedded_bitmap(f, _ezfc_config_parse_bool_node(node->children));
					} else if (xmlStrcmp(attr, (const xmlChar *)FC_HINTING) == 0) {
						ezfc_font_set_hinting(f, _ezfc_config_parse_bool_node(node->children));
					} else if (xmlStrcmp(attr, (const xmlChar *)FC_HINT_STYLE) == 0) {
						ezfc_xml_const_t hintstyle[] = {
							{"hintnone", EZFC_FONT_HINTSTYLE_NONE},
							{"hintslight", EZFC_FONT_HINTSTYLE_SLIGHT},
							{"hintmedium", EZFC_FONT_HINTSTYLE_MEDIUM},
							{"hintfull", EZFC_FONT_HINTSTYLE_FULL},
							{NULL, 0}
						};
						gint ret;

						if (!_ezfc_config_parse_const_node(node->children, hintstyle, &ret)) {
							ret = EZFC_FONT_HINTSTYLE_UNKNOWN;
						}
						ezfc_font_set_hintstyle(f, ret);
					} else if (xmlStrcmp(attr, (const xmlChar *)FC_RGBA) == 0) {
						ezfc_xml_const_t rgba[] = {
							{"unknown", FC_RGBA_UNKNOWN},
							{"rgb", FC_RGBA_RGB},
							{"bgr", FC_RGBA_BGR},
							{"vrgb", FC_RGBA_VRGB},
							{"vbgr", FC_RGBA_VBGR},
							{"none", FC_RGBA_NONE},
							{NULL, 0}
						};
						gint ret;

						if (!_ezfc_config_parse_const_node(node->children, rgba, &ret)) {
							ret = FC_RGBA_UNKNOWN;
						}
						ezfc_font_set_rgba(f, ret);
					} else {
						g_warning("Unexpected value in the name attribute: %s",
							  attr);
					}
					xmlFree(attr);
				}
			} else {
				g_warning("Unexpected element in <match target=\"font\">: %s\n", node->name);
			}
		  bail3:
			node = node->next;
		}
		if (f)
			ezfc_config_add_font(config, f);
		ezfc_font_unref(f);
	  bail4:
		if (err)
			break;
	}
  bail:
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}
	if (xobj)
		xmlXPathFreeObject(xobj);
	if (xctxt)
		xmlXPathFreeContext(xctxt);
	if (doc)
		xmlFreeDoc(doc);
	xmlCleanupParser();

	return retval;
}

static gchar *
_ezfc_config_get_conf_dir(ezfc_config_t *config)
{
	const gchar *xdgcfgdir = g_get_user_config_dir();
	gchar *fcconfdir = g_build_filename(xdgcfgdir, "fontconfig", "conf.d", NULL);

	return fcconfdir;
}

static gchar *
_ezfc_config_get_old_conf_dir(ezfc_config_t *config)
{
	const gchar *homedir = g_get_home_dir();
	gchar *fcconfdir = g_build_filename(homedir, ".fonts.conf.d", NULL);

	return fcconfdir;
}

static gchar *
_ezfc_config_get_conf_filename(ezfc_config_t *config)
{
	gchar *confname;

	if (config->name)
		confname = g_strdup_printf("%03d-%s-ezfc.conf", config->priority, config->name);
	else
		confname = g_strdup_printf("%03d-ezfc.conf", config->priority);

	return confname;
}

static gchar *
_ezfc_config_get_conf_name(ezfc_config_t *config)
{
	gchar *fcconfdir = _ezfc_config_get_conf_dir(config);
	gchar *confname = _ezfc_config_get_conf_filename(config);
	gchar *ezfcconf = g_build_filename(fcconfdir, confname, NULL);

	g_free(confname);
	g_free(fcconfdir);

	return ezfcconf;
}

static gchar *
_ezfc_config_get_old_conf_name(ezfc_config_t *config)
{
	gchar *fcconfdir = _ezfc_config_get_old_conf_dir(config);
	gchar *confname = _ezfc_config_get_conf_filename(config);
	gchar *ezfcconf = g_build_filename(fcconfdir, confname, NULL);

	g_free(confname);
	g_free(fcconfdir);

	return ezfcconf;
}

/*< public >*/

/**
 * ezfc_config_new:
 *
 * Create a new instance of a #ezfc_config_t.
 *
 * Returns: (transfer full): a new instance of #ezfc_config_t.
 */
ezfc_config_t *
ezfc_config_new(void)
{
	ezfc_config_t *retval = ezfc_mem_alloc_object(sizeof (ezfc_config_t));

	if (retval) {
		retval->aliases = g_hash_table_new_full(g_str_hash,
							g_str_equal,
							g_free,
							(GDestroyNotify)ezfc_container_unref);
		retval->fonts = g_hash_table_new_full(g_str_hash,
						      g_str_equal,
						      g_free,
						      (GDestroyNotify)ezfc_font_unref);
		retval->subst = g_hash_table_new_full(g_str_hash,
						      g_str_equal,
						      g_free,
						      (GDestroyNotify)ezfc_container_unref);
		retval->name = NULL;
		retval->priority = 0;
		retval->loaded = FALSE;
		retval->migration = TRUE;
		ezfc_mem_add_ref(&retval->parent, retval->aliases,
				 (ezfc_destroy_func_t)g_hash_table_destroy);
		ezfc_mem_add_ref(&retval->parent, retval->fonts,
				 (ezfc_destroy_func_t)g_hash_table_destroy);
		ezfc_mem_add_ref(&retval->parent, retval->subst,
				 (ezfc_destroy_func_t)g_hash_table_destroy);
	}

	return retval;
}

/**
 * ezfc_config_ref:
 * @config: a #ezfc_config_t.
 *
 * Increases the refernce count of @config.
 *
 * Returns: (transfer none): the same @config object.
 */
ezfc_config_t *
ezfc_config_ref(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, NULL);

	return ezfc_mem_ref(&config->parent);
}

/**
 * ezfc_config_unref:
 * @config: a #ezfc_config_t.
 *
 * Decreases the reference count of @config. when its reference count
 * drops to 0, the object is finalized (i.e. its memory is freed).
 */
void
ezfc_config_unref(ezfc_config_t *config)
{
	if (config)
		ezfc_mem_unref(&config->parent);
}

/**
 * ezfc_config_set_priority:
 * @config: a #ezfc_config_t.
 * @priority: a priority number that is used for a filename. it has to be
 *            within 3 digits. so the maximum value is 999.
 *
 * Set @priority to @config instance.
 */
void
ezfc_config_set_priority(ezfc_config_t *config,
			 guint          priority)
{
	g_return_if_fail (config != NULL);
	g_return_if_fail (priority < 1000);

	config->priority = priority;
}

/**
 * ezfc_config_get_priority:
 * @config: a #ezfc_config_t.
 *
 * Obtains the priority number in @config.
 *
 * Returns: the priority number. if any errors happens, returns -1.
 */
gint
ezfc_config_get_priority(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, -1);

	return config->priority;
}

/**
 * ezfc_config_set_name:
 * @config: a #ezfc_config_t.
 * @name: (allow-none): additional configuration name.
 *
 * Set @name as the additional configuration name. this is an optional to
 * make the change in the filename for output.
 */
void
ezfc_config_set_name(ezfc_config_t *config,
		     const gchar   *name)
{
	g_return_if_fail (config != NULL);

	if (config->name)
		ezfc_mem_remove_ref(&config->parent, config->name);
	config->name = g_strdup(name);
	if (config->name)
		ezfc_mem_add_ref(&config->parent, config->name,
				 (ezfc_destroy_func_t)g_free);
}

/**
 * ezfc_config_get_name:
 * @config: a #ezfc_config_t.
 *
 * Obtains the configuration name that is set by ezfc_config_set_name().
 *
 * Returns: the configuration name.
 */
const gchar *
ezfc_config_get_name(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, NULL);

	return config->name;
}

/**
 * ezfc_config_add_alias:
 * @config: a #ezfc_config_t.
 * @language: (allow-none): a language name to add @alias for or %NULL for global settings.
 * @alias: a #ezfc_alias_t.
 *
 * Add a @alias font for @language language. if giving %NULL to @language,
 * @alias takes effect for any languages.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 */
gboolean
ezfc_config_add_alias(ezfc_config_t *config,
		      const gchar   *language,
		      ezfc_alias_t  *alias)
{
	gchar *lang;
	ezfc_container_t *container;
	GList *list, *l;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (alias != NULL, FALSE);
	g_return_val_if_fail (ezfc_alias_get_font(alias) != NULL, FALSE);

	if (language)
		lang = g_strdup(language);
	else
		lang = g_strdup("");
	container = g_hash_table_lookup(config->aliases, lang);
	if (!container) {
		container = ezfc_container_new((ezfc_destroy_func_t)_ezfc_config_alias_list_free);
		g_hash_table_insert(config->aliases,
				    g_strdup(lang),
				    container);
	}
	l = list = ezfc_container_get(container);
	while (l != NULL) {
		ezfc_alias_t *a = l->data;

		if (g_ascii_strcasecmp(ezfc_alias_get_name(a),
				       ezfc_alias_get_name(alias)) == 0) {
			l->data = ezfc_alias_ref(alias);
			ezfc_alias_unref(a);
			goto bail;
		}
		if (g_list_next(l) == NULL)
			break;
		l = g_list_next(l);
	}
	l = g_list_append(l, ezfc_alias_ref(alias));
  bail:
	if (!list)
		list = l;
	ezfc_container_set(container, list);
	g_free(lang);

	return TRUE;
}

/**
 * ezfc_config_remove_alias:
 * @config: a #ezfc_config_t.
 * @language: (allow-none): a language name to remove @alias_name from.
 * @alias_name: a alias font name to remove.
 *
 * Removes @alias_name assigned for @language language from @config.
 *
 * Returns: %TRUE if it's successfully removed, otherwise %FALSE.
 */
gboolean
ezfc_config_remove_alias(ezfc_config_t *config,
			 const gchar   *language,
			 const gchar   *alias_name)
{
	ezfc_container_t *container;
	GList *list, *l;
	gchar *alias, *lang;
	gboolean retval = TRUE;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (alias_name != NULL, FALSE);

	if (language)
		lang = g_strdup(language);
	else
		lang = g_strdup("");
	if ((container = g_hash_table_lookup(config->aliases, lang)) == NULL) {
		g_free(lang);

		return FALSE;
	}

	if (g_ascii_strcasecmp(alias_name, "sans") == 0)
		alias = g_strdup("sans-serif");
	else
		alias = g_strdup(alias_name);

	list = ezfc_container_get(container);

	for (l = list; l != NULL; l = g_list_next(l)) {
		ezfc_alias_t *a = l->data;

		if (g_ascii_strcasecmp(ezfc_alias_get_name(a),
				       alias) == 0) {
			if (g_list_length(list) == 1) {
				g_hash_table_remove(config->aliases, lang);
			} else {
				if (l == list) {
					list = g_list_delete_link(l, l);
					ezfc_container_set(container, list);
				} else {
					l = g_list_delete_link(l, l);
				}
				ezfc_alias_unref(a);
			}
			goto bail;
		}
	}
	retval = FALSE;
  bail:
	g_free(alias);
	g_free(lang);

	return retval;
}

/**
 * ezfc_config_remove_aliases:
 * @config: a #ezfc_config_t.
 * @language: (allow-none): a language name to remove @alias_name from.
 *
 * Removes all of aliases assigned for @language language from @config.
 *
 * Returns: %TRUE if it's successfully removed, otherwise %FALSE.
 */
gboolean
ezfc_config_remove_aliases(ezfc_config_t *config,
			   const gchar   *language)
{
	gboolean retval;
	gchar *lang;

	g_return_val_if_fail (config != NULL, FALSE);

	if (language)
		lang = g_strdup(language);
	else
		lang = g_strdup("");

	retval = g_hash_table_remove(config->aliases, lang);
	g_free(lang);

	return retval;
}

/**
 * ezfc_config_add_font:
 * @config: a #ezfc_config_t.
 * @font: a #ezfc_font_t.
 *
 * Add a #font font to generate non-language-specific, non-alias-specific
 * rules.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 */
gboolean
ezfc_config_add_font(ezfc_config_t *config,
		     ezfc_font_t   *font)
{
	const gchar *family;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (font != NULL, FALSE);

	family = ezfc_font_get_family(font);
	g_hash_table_replace(config->fonts, g_strdup(family), ezfc_font_ref(font));

	return TRUE;
}

/**
 * ezfc_config_remove_font:
 * @config: a #ezfc_config_t.
 * @family: a family name to be removed.
 *
 * Remove a #ezfc_font_t instance corresponding to @family.
 *
 * Returns: %TRUE if it's successfully removed, otherwise %FALSE.
 */
gboolean
ezfc_config_remove_font(ezfc_config_t *config,
			const gchar   *family)
{
	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (family != NULL, FALSE);

	return g_hash_table_remove(config->fonts, family);
}

/**
 * ezfc_config_remove_fonts:
 * @config: a #ezfc_config_t.
 *
 * Remove all of fonts from @config, which added by ezfc_config_add_font().
 *
 * Returns: %TRUE if it's successfully removed, otherwise %FALSE.
 */
gboolean
ezfc_config_remove_fonts(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, FALSE);

	g_hash_table_remove_all(config->fonts);

	return TRUE;
}

/**
 * ezfc_config_add_subst:
 * @config: a #ezfc_config_t.
 * @family_name: a family name to be substituted.
 * @subst: a #ezfc_font_t for substitute font
 *
 * Add a #subst font as a substitute font for @family_name.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_config_add_subst(ezfc_config_t *config,
		      const gchar   *family_name,
		      ezfc_font_t   *subst)
{
	ezfc_container_t *container;
	GList *list, *l;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (family_name != NULL && family_name[0], FALSE);
	g_return_val_if_fail (subst != NULL, FALSE);

	container = g_hash_table_lookup(config->subst, family_name);
	if (!container) {
		container = ezfc_container_new((ezfc_destroy_func_t)_ezfc_config_subst_list_free);
		g_hash_table_insert(config->subst,
				    g_strdup(family_name),
				    container);
	}
	l = list = ezfc_container_get(container);
	while (l != NULL) {
		ezfc_font_t *f = l->data;

		if (g_ascii_strcasecmp(ezfc_font_get_family(f),
				       ezfc_font_get_family(subst)) == 0) {
			l->data = ezfc_font_ref(subst);
			ezfc_font_unref(f);
			goto bail;
		}
		if (g_list_next(l) == NULL)
			break;
		l = g_list_next(l);
	}
	l = g_list_append(l, ezfc_font_ref(subst));
  bail:
	if (!list)
		list = l;
	ezfc_container_set(container, list);

	return TRUE;
}

/**
 * ezfc_config_remove_subst:
 * @config: a #ezfc_config_t.
 * @family_name: a family name to be substituted.
 * @subst_name: a substitute font name
 *
 * Remove @subst_name from the substitute font list of @family_name.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_config_remove_subst(ezfc_config_t *config,
			 const gchar   *family_name,
			 const gchar   *subst_name)
{
	ezfc_container_t *container;
	GList *list, *l;
	gboolean retval = TRUE;
	GError *err = NULL;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (family_name != NULL && family_name[0], FALSE);
	g_return_val_if_fail (subst_name != NULL && subst_name[0], FALSE);

	if ((container = g_hash_table_lookup(config->subst, family_name)) == NULL)
		return FALSE;

	list = ezfc_container_get(container);
	for (l = list; l != NULL; l = g_list_next(l)) {
		ezfc_font_t *f = l->data;

		if (ezfc_font_remove_family(f, subst_name, &err)) {
			GList *ll = ezfc_font_get_families(f);

			if (!ll) {
				if (g_list_length(list) == 1) {
					g_hash_table_remove(config->subst, family_name);
				} else {
					if (l == list) {
						list = g_list_delete_link(l, l);
						ezfc_container_set(container, list);
					} else {
						l = g_list_delete_link(l, l);
					}
					ezfc_font_unref(f);
				}
			} else {
				g_list_free(ll);
			}
			goto bail;
		}
	}
	retval = FALSE;
  bail:

	return retval;
}

/**
 * ezfc_config_remove_substs:
 * @config: a #ezfc_config_t.
 * @family_name: a family name to be substituted.
 *
 * Remove all of substitute font list of @family_name.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 *
 * Since: 0.11
 */
gboolean
ezfc_config_remove_substs(ezfc_config_t *config,
			  const gchar   *family_name)
{
	gboolean retval;

	g_return_val_if_fail (config != NULL, FALSE);
	g_return_val_if_fail (family_name != NULL && family_name[0], FALSE);

	retval = g_hash_table_remove(config->subst, family_name);

	return retval;
}

/**
 * ezfc_config_get_language_list:
 * @config: a #ezfc_config_t.
 *
 * Obtains the list of languages registered by ezfc_config_add_alias() in @config.
 *
 * Returns: (element-type utf8) (transfer none): a #GList contains languages or %NULL.
 */
GList *
ezfc_config_get_language_list(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, NULL);

	return g_hash_table_get_keys(config->aliases);
}

/**
 * ezfc_config_get_aliases:
 * @config: a #ezfc_config_t.
 * @language: (allow-none): a language name referenced to the alias
 *
 * Obtains the list of #ezfc_alias_t in #ezfc_config_t instance.
 *
 * Returns: (element-type ezfc_alias_t) (transfer none): a #GList contains #ezfc_alias_t or %NULL.
 */
const GList *
ezfc_config_get_aliases(ezfc_config_t *config,
			const gchar   *language)
{
	ezfc_container_t *container;
	const GList *retval;
	gchar *lang;

	g_return_val_if_fail (config != NULL, NULL);

	if (language)
		lang = g_strdup(language);
	else
		lang = g_strdup("");
	container = g_hash_table_lookup(config->aliases, lang);
	if (!container)
		retval = NULL;
	else
		retval = ezfc_container_get(container);
	g_free(lang);

	return retval;
}

/**
 * ezfc_config_get_fonts:
 * @config: a #ezfc_config_t.
 *
 * Obtains the list of #ezfc_font_t in @config.
 *
 * Returns: (element-type ezfc_font_t) (transfer container): a #GList contains #ezfc_font_t or %NULL.
 */
GList *
ezfc_config_get_fonts(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, NULL);

	return g_hash_table_get_values(config->fonts);
}

/**
 * ezfc_config_get_subst_family:
 * @config: a #ezfc_config_t.
 *
 * Obtains the list of the family name being substituted
 *
 * Returns: (element-type utf8) (transfer none): a #GList contains languages or %NULL.
 *
 * Since: 0.11
 */
GList *
ezfc_config_get_subst_family(ezfc_config_t *config)
{
	g_return_val_if_fail (config != NULL, NULL);

	return g_hash_table_get_keys(config->subst);
}

/**
 * ezfc_config_get_substs:
 * @config: a #ezfc_config_t.
 * @family_name: a family name being substituted.
 *
 * Obtains the list of #ezfc_font_t to be substituted for @family_name.
 *
 * Returns: (element-type ezfc_font_t) (transfer none): a #GList contains #ezfc_font_t or %NULL.
 *
 * Since: 0.11
 */
const GList *
ezfc_config_get_substs(ezfc_config_t *config,
		       const gchar   *family_name)
{
	ezfc_container_t *container;
	const GList *retval;

	g_return_val_if_fail (config != NULL, NULL);
	g_return_val_if_fail (family_name != NULL && family_name[0], NULL);

	container = g_hash_table_lookup(config->subst, family_name);
	if (!container)
		retval = NULL;
	else
		retval = ezfc_container_get(container);

	return retval;
}

/**
 * ezfc_config_load:
 * @config: a #ezfc_config_t.
 * @error: (allow-none): a #GError.
 *
 * Read the configuration file and rebuild the object.
 * You have to invoke ezfc_config_set_priority() and ezfc_config_set_name()
 * first to read the appropriate configuration file.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 */
gboolean
ezfc_config_load(ezfc_config_t  *config,
		 GError        **error)
{
	gchar *ezfcconf;
	gboolean retval = TRUE;
	GError *err = NULL;

	g_return_val_if_fail (config != NULL, FALSE);

	if (config->migration) {
		ezfcconf = _ezfc_config_get_old_conf_name(config);
		if (!g_file_test(ezfcconf, G_FILE_TEST_EXISTS))
			goto try_new_one;
	} else {
	  try_new_one:
		ezfcconf = _ezfc_config_get_conf_name(config);
		if (!g_file_test(ezfcconf, G_FILE_TEST_IS_REGULAR)) {
			g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_CONFIG_FILE,
				    "No such config file available: %s",
				    ezfcconf);
			retval = FALSE;
			goto bail;
		}
	}
	if (!(retval = _ezfc_config_from_xml(config, ezfcconf, &err)))
		goto bail;
	config->loaded = TRUE;

  bail:
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}
	g_free(ezfcconf);

	return retval;
}

/**
 * ezfc_config_save:
 * @config: a #ezfc_config_t.
 * @error: (allow-none): a #GError.
 *
 * Write the data to the configuration file. you may want to invoke
 * ezfc_config_set_priority() and ezfc_config_set_name() first to
 * write it to the appropriate configuration file.
 *
 * Returns: %TRUE if it's successfully completed, otherwise %FALSE.
 */
gboolean
ezfc_config_save(ezfc_config_t  *config,
		 GError        **error)
{
	GString *xml = NULL;
	gchar *ezfcconf, *fcconfdir;
	GError *err = NULL;
	gboolean retval = TRUE;

	g_return_val_if_fail (config != NULL, FALSE);

	fcconfdir = _ezfc_config_get_conf_dir(config);
	ezfcconf = _ezfc_config_get_conf_name(config);
	if (!g_file_test(fcconfdir, G_FILE_TEST_EXISTS)) {
		g_mkdir_with_parents(fcconfdir, 0755);
	} else {
		if (!g_file_test(fcconfdir, G_FILE_TEST_IS_DIR)) {
			g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_CONFIG_DIR,
				    "Unable to create a config dir: %s",
				    fcconfdir);
			retval = FALSE;
			goto bail;
		}
	}
	if ((xml = ezfc_config_save_to_buffer(config, &err)) == NULL) {
		if (config->loaded) {
			/* elements might be removed. */
			if (g_unlink(ezfcconf) == -1) {
				if (config->migration) {
					goto cleanup;
				} else {
					g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_LIBC,
						    "%s", g_strerror(errno));
				}
			} else {
				g_clear_error(&err);
			}
		}
		goto bail;
	}

	if (!g_file_set_contents(ezfcconf, xml->str, xml->len, &err))
		goto bail;
	if (config->migration) {
		gchar *fn, *olddir;

	  cleanup:
		fn = _ezfc_config_get_old_conf_name(config);
		olddir = _ezfc_config_get_old_conf_dir(config);
		/* Do not try to remove if the directory is symlink */
		if (g_file_test(fn, G_FILE_TEST_EXISTS) && !g_file_test(olddir, G_FILE_TEST_IS_SYMLINK)) {
			if (g_unlink(fn) == -1) {
				g_set_error(&err, EZFC_ERROR, EZFC_ERR_FAIL_ON_LIBC,
					    "%s", g_strerror(errno));
			}
		}
	}
  bail:
	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	}
	g_free(fcconfdir);
	g_free(ezfcconf);
	if (xml)
		g_string_free(xml, TRUE);

	return retval;
}

/**
 * ezfc_config_save_to_buffer:
 * @config: a #ezfc_config_t.
 * @error: (allow-none): a #GError.
 *
 * Write the data to the buffer.
 *
 * Returns: a #GString containing a xml data. %NULL if fails.
 */
GString *
ezfc_config_save_to_buffer(ezfc_config_t  *config,
			   GError        **error)
{
	xmlChar *xml;
	int size;
	GError *err = NULL;
	GString *retval = NULL;

	g_return_val_if_fail (config != NULL, FALSE);

	if (!_ezfc_config_to_xml(config, &xml, &size)) {
		g_set_error(&err, EZFC_ERROR, EZFC_ERR_NO_ELEMENTS,
			    "No configuration to output");
	}

	if (err) {
		if (error)
			*error = g_error_copy(err);
		else
			g_warning("%s", err->message);
		g_error_free(err);
	} else {
		retval = g_string_new((const gchar *)xml);
	}
	xmlFree(xml);

	return retval;
}

/**
 * ezfc_config_dump:
 * @config: a #ezfc_config_t.
 *
 * Output the object data to the standard output.
 */
void
ezfc_config_dump(ezfc_config_t *config)
{
	GHashTableIter iter;
	gpointer key, val;

	g_return_if_fail (config != NULL);

	g_hash_table_iter_init(&iter, config->aliases);
	while (g_hash_table_iter_next(&iter, &key, &val)) {
		ezfc_container_t *container = val;
		const gchar *lang = key;
		GList *l = ezfc_container_get(container);

		if (lang[0] == 0)
			g_print("<any>:\n");
		else
			g_print("%s:\n", lang);
		for (; l != NULL; l = g_list_next(l)) {
			ezfc_alias_t *a = l->data;
			FcChar8 *result;
			FcPattern *pat;

			pat = ezfc_alias_get_font_pattern(a);
			result = FcPatternFormat(pat,
						 (FcChar8 *)"%{=unparse}");
			g_print("\t%s: %s\n",
				ezfc_alias_get_name(a),
				result);
			FcStrFree(result);
			FcPatternDestroy(pat);
		}
	}
}

/**
 * ezfc_config_set_migration:
 * @config: a #ezfc_config_t.
 * @flag: a #gboolean.
 *
 * Set a flag to migrate the configuration file on the older place to
 * the new one where XDG Base Directory Specification defines.
 * If @flag is %TRUE, ezfc_config_load() will tries to read the config
 * file from the old path prior to new place and the old file will be
 * removed during ezfc_config_save().
 *
 * This feature is enabled by default.
 *
 * Since: 0.8
*/
void
ezfc_config_set_migration(ezfc_config_t *config,
			  gboolean       flag)
{
	g_return_if_fail (config != NULL);

	config->migration = (flag == TRUE);
}