static void
fcitx_config_widget_set_property(GObject      *gobject,
                                 guint         prop_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
    FcitxConfigWidget* config_widget = FCITX_CONFIG_WIDGET(gobject);
    switch (prop_id) {
    case PROP_CONFIG_DESC:
        config_widget->cfdesc = g_value_get_pointer(value);
        break;
    case PROP_PREFIX:
        if (config_widget->prefix)
            g_free(config_widget->prefix);
        config_widget->prefix = g_strdup(g_value_get_string(value));
        break;
    case PROP_NAME:
        if (config_widget->name)
            g_free(config_widget->name);
        config_widget->name = g_strdup(g_value_get_string(value));
        break;
    case PROP_SUBCONFIG:
        if (config_widget->parser)
            sub_config_parser_free(config_widget->parser);
        config_widget->parser = sub_config_parser_new(g_value_get_string(value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(gobject, prop_id, pspec);
        break;
    }
}
FcitxSubConfigParser* sub_config_parser_new(const gchar* subconfig)
{
    if (subconfig == NULL)
        return NULL;

    FcitxSubConfigParser* parser = g_malloc0(sizeof(FcitxSubConfigParser));
    parser->subconfigs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, sub_config_pattern_free);
    gchar** strv = g_strsplit(subconfig, ",", 0);

    gchar** str;
    for (str = &strv[0]; *str != NULL; str++) {
        if (strchr(*str, ':') == NULL)
            continue;

        gchar** items = g_strsplit(*str, ":", 0);

        if (g_strv_length(items) < 2)
            goto end;
        if (strlen(items[0]) == 0)
            goto end;

        if (strcmp(items[1], "domain") == 0) {
            parser->domain = g_strdup(items[0]);
            goto end;
        }

        SubConfigType type = parse_type(items[1]);
        if (type == SC_None)
            goto end;
        if (g_hash_table_lookup(parser->subconfigs, items[0]) != NULL)
            continue;

        if (type == SC_ConfigFile) {
            if (g_strv_length(items) != 4)
                goto end;
            if (strlen(items[2]) == 0 || items[2][0] == '/')
                goto end;
        } else if (type == SC_NativeFile) {
            if (g_strv_length(items) != 3)
                goto end;
            if (strchr(items[2], '*') != NULL)
                goto end;
        }

        gchar** paths = g_strsplit(items[2], "/", 0);
        if (paths[0] == 0) {
            g_strfreev(paths);
            goto end;
        }
        gchar** path;
        for (path = &paths[0]; *path != NULL; path++) {
            if (strlen(*path) == 0)
                break;
            if (strcmp(*path, ".") == 0)
                break;
            if (strcmp(*path, "..") == 0)
                break;
        }
        if (*path != NULL) {
            g_strfreev(paths);
            goto end;
        }
        FcitxSubConfigPattern* pattern = g_malloc0(sizeof(FcitxSubConfigPattern));
        pattern->type = type;
        pattern->patternlist = paths;
        if (type == SC_ConfigFile)
            pattern->configdesc = g_strdup(items[3]);
        else if (type == SC_NativeFile)
            pattern->nativepath = g_strdup(items[2]);

        g_hash_table_insert(parser->subconfigs, g_strdup(items[0]), pattern);
    end:
        g_strfreev(items);
    }
    g_strfreev(strv);
    if (g_hash_table_size(parser->subconfigs) == 0 || parser->domain == NULL) {
        sub_config_parser_free(parser);
        parser = NULL;
    }

    return parser;
}