/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * This program 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 of the License, or (at your option) version 3. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Not Zed * Jeffrey Stedfast * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "e-alert-dialog.h" #include "e-filter-code.h" #include "e-filter-color.h" #include "e-filter-datespec.h" #include "e-filter-file.h" #include "e-filter-input.h" #include "e-filter-int.h" #include "e-filter-option.h" #include "e-filter-rule.h" #include "e-rule-context.h" #include "e-xml-utils.h" #define E_RULE_CONTEXT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_RULE_CONTEXT, ERuleContextPrivate)) struct _ERuleContextPrivate { gint frozen; }; enum { RULE_ADDED, RULE_REMOVED, CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL]; struct _revert_data { GHashTable *rules; gint rank; }; G_DEFINE_TYPE ( ERuleContext, e_rule_context, G_TYPE_OBJECT) static void rule_context_set_error (ERuleContext *context, gchar *error) { g_free (context->error); context->error = error; } static void new_rule_response (GtkWidget *dialog, gint button, ERuleContext *context) { if (button == GTK_RESPONSE_OK) { EFilterRule *rule = g_object_get_data ((GObject *) dialog, "rule"); gchar *user = g_object_get_data ((GObject *) dialog, "path"); EAlert *alert = NULL; if (!e_filter_rule_validate (rule, &alert)) { e_alert_run_dialog (GTK_WINDOW (dialog), alert); g_object_unref (alert); return; } if (e_rule_context_find_rule (context, rule->name, rule->source)) { e_alert_run_dialog_for_args ((GtkWindow *) dialog, "filter:bad-name-notunique", rule->name, NULL); return; } g_object_ref (rule); e_rule_context_add_rule (context, rule); if (user) e_rule_context_save (context, user); } gtk_widget_destroy (dialog); } static void revert_rule_remove (gpointer key, EFilterRule *rule, ERuleContext *context) { e_rule_context_remove_rule (context, rule); g_object_unref (rule); } static void revert_source_remove (gpointer key, struct _revert_data *rest_data, ERuleContext *context) { g_hash_table_foreach ( rest_data->rules, (GHFunc) revert_rule_remove, context); g_hash_table_destroy (rest_data->rules); g_free (rest_data); } static guint source_hashf (const gchar *a) { return (a != NULL) ? g_str_hash (a) : 0; } static gint source_eqf (const gchar *a, const gchar *b) { return (g_strcmp0 (a, b) == 0); } static void free_part_set (struct _part_set_map *map) { g_free (map->name); g_free (map); } static void free_rule_set (struct _rule_set_map *map) { g_free (map->name); g_free (map); } static void rule_context_finalize (GObject *obj) { ERuleContext *context =(ERuleContext *) obj; g_list_foreach (context->rule_set_list, (GFunc) free_rule_set, NULL); g_list_free (context->rule_set_list); g_hash_table_destroy (context->rule_set_map); g_list_foreach (context->part_set_list, (GFunc) free_part_set, NULL); g_list_free (context->part_set_list); g_hash_table_destroy (context->part_set_map); g_free (context->error); g_list_foreach (context->parts, (GFunc) g_object_unref, NULL); g_list_free (context->parts); g_list_foreach (context->rules, (GFunc) g_object_unref, NULL); g_list_free (context->rules); G_OBJECT_CLASS (e_rule_context_parent_class)->finalize (obj); } static gint rule_context_load (ERuleContext *context, const gchar *system, const gchar *user) { xmlNodePtr set, rule, root; xmlDocPtr systemdoc, userdoc; struct _part_set_map *part_map; struct _rule_set_map *rule_map; rule_context_set_error (context, NULL); systemdoc = e_xml_parse_file (system); if (systemdoc == NULL) { gchar * err_msg; err_msg = g_strdup_printf ( "Unable to load system rules '%s': %s", system, g_strerror (errno)); g_warning ("%s: %s", G_STRFUNC, err_msg); rule_context_set_error (context, err_msg); /* no need to free err_msg here */ return -1; } root = xmlDocGetRootElement (systemdoc); if (root == NULL || strcmp ((gchar *) root->name, "filterdescription")) { gchar * err_msg; err_msg = g_strdup_printf ( "Unable to load system rules '%s': " "Invalid format", system); g_warning ("%s: %s", G_STRFUNC, err_msg); rule_context_set_error (context, err_msg); /* no need to free err_msg here */ xmlFreeDoc (systemdoc); return -1; } /* doesn't matter if this doens't exist */ userdoc = NULL; if (g_file_test (user, G_FILE_TEST_IS_REGULAR)) userdoc = e_xml_parse_file (user); /* now parse structure */ /* get rule parts */ set = root->children; while (set) { part_map = g_hash_table_lookup (context->part_set_map, set->name); if (part_map) { rule = set->children; while (rule) { if (!strcmp ((gchar *) rule->name, "part")) { EFilterPart *part = E_FILTER_PART (g_object_new ( part_map->type, NULL, NULL)); if (e_filter_part_xml_create (part, rule, context) == 0) { part_map->append (context, part); } else { g_object_unref (part); g_warning ("Cannot load filter part"); } } rule = rule->next; } } else if ((rule_map = g_hash_table_lookup ( context->rule_set_map, set->name))) { rule = set->children; while (rule) { if (!strcmp ((gchar *) rule->name, "rule")) { EFilterRule *part = E_FILTER_RULE (g_object_new ( rule_map->type, NULL, NULL)); if (e_filter_rule_xml_decode (part, rule, context) == 0) { part->system = TRUE; rule_map->append (context, part); } else { g_object_unref (part); g_warning ("Cannot load filter part"); } } rule = rule->next; } } set = set->next; } /* now load actual rules */ if (userdoc) { root = xmlDocGetRootElement (userdoc); set = root ? root->children : NULL; while (set) { rule_map = g_hash_table_lookup (context->rule_set_map, set->name); if (rule_map) { rule = set->children; while (rule) { if (!strcmp ((gchar *) rule->name, "rule")) { EFilterRule *part = E_FILTER_RULE (g_object_new ( rule_map->type, NULL, NULL)); if (e_filter_rule_xml_decode (part, rule, context) == 0) { rule_map->append (context, part); } else { g_object_unref (part); g_warning ("Cannot load filter part"); } } rule = rule->next; } } set = set->next; } } xmlFreeDoc (userdoc); xmlFreeDoc (systemdoc); return 0; } static gint rule_context_save (ERuleContext *context, const gchar *user) { xmlDocPtr doc; xmlNodePtr root, rules, work; GList *l; EFilterRule *rule; struct _rule_set_map *map; gint ret; doc = xmlNewDoc ((xmlChar *)"1.0"); /* FIXME: set character encoding to UTF-8? */ root = xmlNewDocNode (doc, NULL, (xmlChar *)"filteroptions", NULL); xmlDocSetRootElement (doc, root); l = context->rule_set_list; while (l) { map = l->data; rules = xmlNewDocNode (doc, NULL, (xmlChar *) map->name, NULL); xmlAddChild (root, rules); rule = NULL; while ((rule = map->next (context, rule, NULL))) { if (!rule->system) { work = e_filter_rule_xml_encode (rule); xmlAddChild (rules, work); } } l = g_list_next (l); } ret = e_xml_save_file (user, doc); xmlFreeDoc (doc); return ret; } static gint rule_context_revert (ERuleContext *context, const gchar *user) { xmlNodePtr set, rule; /*struct _part_set_map *part_map;*/ struct _rule_set_map *rule_map; struct _revert_data *rest_data; GHashTable *source_hash; xmlDocPtr userdoc; EFilterRule *frule; rule_context_set_error (context, NULL); userdoc = e_xml_parse_file (user); if (userdoc == NULL) /* clear out anythign we have? */ return 0; source_hash = g_hash_table_new ( (GHashFunc) source_hashf, (GCompareFunc) source_eqf); /* setup stuff we have now */ /* Note that we assume there is only 1 set of rules in a given rule context, * although other parts of the code dont assume this */ frule = NULL; while ((frule = e_rule_context_next_rule (context, frule, NULL))) { rest_data = g_hash_table_lookup (source_hash, frule->source); if (rest_data == NULL) { rest_data = g_malloc0 (sizeof (*rest_data)); rest_data->rules = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (source_hash, frule->source, rest_data); } g_hash_table_insert (rest_data->rules, frule->name, frule); } /* make what we have, match what we load */ set = xmlDocGetRootElement (userdoc); set = set ? set->children : NULL; while (set) { rule_map = g_hash_table_lookup (context->rule_set_map, set->name); if (rule_map) { rule = set->children; while (rule) { if (!strcmp ((gchar *) rule->name, "rule")) { EFilterRule *part = E_FILTER_RULE (g_object_new ( rule_map->type, NULL, NULL)); if (e_filter_rule_xml_decode (part, rule, context) == 0) { /* Use the revert data to keep * track of the right rank of * this rule part. */ rest_data = g_hash_table_lookup (source_hash, part->source); if (rest_data == NULL) { rest_data = g_malloc0 (sizeof (*rest_data)); rest_data->rules = g_hash_table_new ( g_str_hash, g_str_equal); g_hash_table_insert ( source_hash, part->source, rest_data); } frule = g_hash_table_lookup ( rest_data->rules, part->name); if (frule) { if (context->priv->frozen == 0 && !e_filter_rule_eq (frule, part)) e_filter_rule_copy (frule, part); g_object_unref (part); e_rule_context_rank_rule ( context, frule, frule->source, rest_data->rank); g_hash_table_remove (rest_data->rules, frule->name); } else { e_rule_context_add_rule (context, part); e_rule_context_rank_rule ( context, part, part->source, rest_data->rank); } rest_data->rank++; } else { g_object_unref (part); g_warning ("Cannot load filter part"); } } rule = rule->next; } } set = set->next; } xmlFreeDoc (userdoc); /* remove any we still have that weren't in the file */ g_hash_table_foreach (source_hash, (GHFunc) revert_source_remove, context); g_hash_table_destroy (source_hash); return 0; } static EFilterElement * rule_context_new_element (ERuleContext *context, const gchar *type) { if (!strcmp (type, "string")) { return (EFilterElement *) e_filter_input_new (); } else if (!strcmp (type, "address")) { /* FIXME: temporary ... need real address type */ return (EFilterElement *) e_filter_input_new_type_name (type); } else if (!strcmp (type, "code")) { return (EFilterElement *) e_filter_code_new (FALSE); } else if (!strcmp (type, "rawcode")) { return (EFilterElement *) e_filter_code_new (TRUE); } else if (!strcmp (type, "colour")) { return (EFilterElement *) e_filter_color_new (); } else if (!strcmp (type, "optionlist")) { return (EFilterElement *) e_filter_option_new (); } else if (!strcmp (type, "datespec")) { return (EFilterElement *) e_filter_datespec_new (); } else if (!strcmp (type, "command")) { return (EFilterElement *) e_filter_file_new_type_name (type); } else if (!strcmp (type, "file")) { return (EFilterElement *) e_filter_file_new_type_name (type); } else if (!strcmp (type, "integer")) { return (EFilterElement *) e_filter_int_new (); } else if (!strcmp (type, "regex")) { return (EFilterElement *) e_filter_input_new_type_name (type); } else if (!strcmp (type, "completedpercent")) { return (EFilterElement *) e_filter_int_new_type ( "completedpercent", 0,100); } else { g_warning ("Unknown filter type '%s'", type); return NULL; } } static void e_rule_context_class_init (ERuleContextClass *class) { GObjectClass *object_class; g_type_class_add_private (class, sizeof (ERuleContextPrivate)); object_class = G_OBJECT_CLASS (class); object_class->finalize = rule_context_finalize; class->load = rule_context_load; class->save = rule_context_save; class->revert = rule_context_revert; class->new_element = rule_context_new_element; signals[RULE_ADDED] = g_signal_new ( "rule-added", E_TYPE_RULE_CONTEXT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ERuleContextClass, rule_added), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[RULE_REMOVED] = g_signal_new ( "rule-removed", E_TYPE_RULE_CONTEXT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ERuleContextClass, rule_removed), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[CHANGED] = g_signal_new ( "changed", E_TYPE_RULE_CONTEXT, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ERuleContextClass, changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void e_rule_context_init (ERuleContext *context) { context->priv = E_RULE_CONTEXT_GET_PRIVATE (context); context->part_set_map = g_hash_table_new (g_str_hash, g_str_equal); context->rule_set_map = g_hash_table_new (g_str_hash, g_str_equal); context->flags = E_RULE_CONTEXT_GROUPING; } /** * e_rule_context_new: * * Create a new ERuleContext object. * * Return value: A new #ERuleContext object. **/ ERuleContext * e_rule_context_new (void) { return g_object_new (E_TYPE_RULE_CONTEXT, NULL); } void e_rule_context_add_part_set (ERuleContext *context, const gchar *setname, GType part_type, ERuleContextPartFunc append, ERuleContextNextPartFunc next) { struct _part_set_map *map; g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (setname != NULL); g_return_if_fail (append != NULL); g_return_if_fail (next != NULL); map = g_hash_table_lookup (context->part_set_map, setname); if (map != NULL) { g_hash_table_remove (context->part_set_map, setname); context->part_set_list = g_list_remove (context->part_set_list, map); free_part_set (map); map = NULL; } map = g_malloc0 (sizeof (*map)); map->type = part_type; map->append = append; map->next = next; map->name = g_strdup (setname); g_hash_table_insert (context->part_set_map, map->name, map); context->part_set_list = g_list_append (context->part_set_list, map); } void e_rule_context_add_rule_set (ERuleContext *context, const gchar *setname, GType rule_type, ERuleContextRuleFunc append, ERuleContextNextRuleFunc next) { struct _rule_set_map *map; g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (setname != NULL); g_return_if_fail (append != NULL); g_return_if_fail (next != NULL); map = g_hash_table_lookup (context->rule_set_map, setname); if (map != NULL) { g_hash_table_remove (context->rule_set_map, setname); context->rule_set_list = g_list_remove (context->rule_set_list, map); free_rule_set (map); map = NULL; } map = g_malloc0 (sizeof (*map)); map->type = rule_type; map->append = append; map->next = next; map->name = g_strdup (setname); g_hash_table_insert (context->rule_set_map, map->name, map); context->rule_set_list = g_list_append (context->rule_set_list, map); } /** * e_rule_context_load: * @context: * @system: * @user: * * Load a rule context from a system and user description file. * * Return value: **/ gint e_rule_context_load (ERuleContext *context, const gchar *system, const gchar *user) { ERuleContextClass *class; gint result; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1); g_return_val_if_fail (system != NULL, -1); g_return_val_if_fail (user != NULL, -1); class = E_RULE_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class->load != NULL, -1); context->priv->frozen++; result = class->load (context, system, user); context->priv->frozen--; return result; } /** * e_rule_context_save: * @context: * @user: * * Save a rule context to disk. * * Return value: **/ gint e_rule_context_save (ERuleContext *context, const gchar *user) { ERuleContextClass *class; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1); g_return_val_if_fail (user != NULL, -1); class = E_RULE_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class->save != NULL, -1); return class->save (context, user); } /** * e_rule_context_revert: * @context: * @user: * * Reverts a rule context from a user description file. Assumes the * system description file is unchanged from when it was loaded. * * Return value: **/ gint e_rule_context_revert (ERuleContext *context, const gchar *user) { ERuleContextClass *class; g_return_val_if_fail (E_RULE_CONTEXT (context), 0); g_return_val_if_fail (user != NULL, 0); class = E_RULE_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class->revert != NULL, 0); return class->revert (context, user); } EFilterPart * e_rule_context_find_part (ERuleContext *context, const gchar *name) { g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (name != NULL, NULL); return e_filter_part_find_list (context->parts, name); } EFilterPart * e_rule_context_create_part (ERuleContext *context, const gchar *name) { EFilterPart *part; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (name != NULL, NULL); part = e_rule_context_find_part (context, name); if (part == NULL) return NULL; return e_filter_part_clone (part); } EFilterPart * e_rule_context_next_part (ERuleContext *context, EFilterPart *last) { g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); return e_filter_part_next_list (context->parts, last); } EFilterRule * e_rule_context_next_rule (ERuleContext *context, EFilterRule *last, const gchar *source) { g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); return e_filter_rule_next_list (context->rules, last, source); } EFilterRule * e_rule_context_find_rule (ERuleContext *context, const gchar *name, const gchar *source) { g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (name != NULL, NULL); return e_filter_rule_find_list (context->rules, name, source); } void e_rule_context_add_part (ERuleContext *context, EFilterPart *part) { g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (E_IS_FILTER_PART (part)); context->parts = g_list_append (context->parts, part); } void e_rule_context_add_rule (ERuleContext *context, EFilterRule *rule) { g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (E_IS_FILTER_RULE (rule)); context->rules = g_list_append (context->rules, rule); if (context->priv->frozen == 0) { g_signal_emit (context, signals[RULE_ADDED], 0, rule); g_signal_emit (context, signals[CHANGED], 0); } } /* Add a rule, with a gui, asking for confirmation first, * and optionally save to path. */ void e_rule_context_add_rule_gui (ERuleContext *context, EFilterRule *rule, const gchar *title, const gchar *path) { GtkDialog *dialog; GtkWidget *widget; GtkWidget *content_area; g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (E_IS_FILTER_RULE (rule)); widget = e_filter_rule_get_widget (rule, context); gtk_widget_show (widget); dialog =(GtkDialog *) gtk_dialog_new (); gtk_dialog_add_buttons ( dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_window_set_title ((GtkWindow *) dialog, title); gtk_window_set_default_size ((GtkWindow *) dialog, 600, 400); gtk_window_set_resizable ((GtkWindow *) dialog, TRUE); content_area = gtk_dialog_get_content_area (dialog); gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0); g_object_set_data_full ((GObject *) dialog, "rule", rule, g_object_unref); if (path) g_object_set_data_full ((GObject *) dialog, "path", g_strdup (path), g_free); g_signal_connect ( dialog, "response", G_CALLBACK (new_rule_response), context); g_object_ref (context); g_object_set_data_full ((GObject *) dialog, "context", context, g_object_unref); gtk_widget_show ((GtkWidget *) dialog); } void e_rule_context_remove_rule (ERuleContext *context, EFilterRule *rule) { g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (E_IS_FILTER_RULE (rule)); context->rules = g_list_remove (context->rules, rule); if (context->priv->frozen == 0) { g_signal_emit (context, signals[RULE_REMOVED], 0, rule); g_signal_emit (context, signals[CHANGED], 0); } } void e_rule_context_rank_rule (ERuleContext *context, EFilterRule *rule, const gchar *source, gint rank) { GList *node; gint i = 0, index = 0; g_return_if_fail (E_IS_RULE_CONTEXT (context)); g_return_if_fail (E_IS_FILTER_RULE (rule)); if (e_rule_context_get_rank_rule (context, rule, source) == rank) return; context->rules = g_list_remove (context->rules, rule); node = context->rules; while (node) { EFilterRule *r = node->data; if (i == rank) { context->rules = g_list_insert (context->rules, rule, index); if (context->priv->frozen == 0) g_signal_emit (context, signals[CHANGED], 0); return; } index++; if (source == NULL || (r->source && strcmp (r->source, source) == 0)) i++; node = node->next; } context->rules = g_list_append (context->rules, rule); if (context->priv->frozen == 0) g_signal_emit (context, signals[CHANGED], 0); } gint e_rule_context_get_rank_rule (ERuleContext *context, EFilterRule *rule, const gchar *source) { GList *node; gint i = 0; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), -1); g_return_val_if_fail (E_IS_FILTER_RULE (rule), -1); node = context->rules; while (node) { EFilterRule *r = node->data; if (r == rule) return i; if (source == NULL || (r->source && strcmp (r->source, source) == 0)) i++; node = node->next; } return -1; } EFilterRule * e_rule_context_find_rank_rule (ERuleContext *context, gint rank, const gchar *source) { GList *node; gint i = 0; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); node = context->rules; while (node) { EFilterRule *r = node->data; if (source == NULL || (r->source && strcmp (r->source, source) == 0)) { if (rank == i) return r; i++; } node = node->next; } return NULL; } GList * e_rule_context_rename_uri (ERuleContext *context, const gchar *old_uri, const gchar *new_uri, GCompareFunc compare) { ERuleContextClass *class; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (old_uri != NULL, NULL); g_return_val_if_fail (new_uri != NULL, NULL); g_return_val_if_fail (compare != NULL, NULL); class = E_RULE_CONTEXT_GET_CLASS (context); /* This method is optional. */ if (class->rename_uri == NULL) return NULL; return class->rename_uri (context, old_uri, new_uri, compare); } GList * e_rule_context_delete_uri (ERuleContext *context, const gchar *uri, GCompareFunc compare) { ERuleContextClass *class; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (uri != NULL, NULL); g_return_val_if_fail (compare != NULL, NULL); class = E_RULE_CONTEXT_GET_CLASS (context); /* This method is optional. */ if (class->delete_uri == NULL) return NULL; return class->delete_uri (context, uri, compare); } void e_rule_context_free_uri_list (ERuleContext *context, GList *uris) { g_return_if_fail (E_IS_RULE_CONTEXT (context)); /* TODO: should be virtual */ g_list_foreach (uris, (GFunc) g_free, NULL); g_list_free (uris); } /** * e_rule_context_new_element: * @context: * @name: * * create a new filter element based on name. * * Return value: **/ EFilterElement * e_rule_context_new_element (ERuleContext *context, const gchar *name) { ERuleContextClass *class; g_return_val_if_fail (E_IS_RULE_CONTEXT (context), NULL); g_return_val_if_fail (name != NULL, NULL); class = E_RULE_CONTEXT_GET_CLASS (context); g_return_val_if_fail (class->new_element != NULL, NULL); return class->new_element (context, name); }