diff options
Diffstat (limited to 'addressbook/backend/ebook/e-book-query.c')
-rw-r--r-- | addressbook/backend/ebook/e-book-query.c | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/addressbook/backend/ebook/e-book-query.c b/addressbook/backend/ebook/e-book-query.c new file mode 100644 index 0000000000..16e325303c --- /dev/null +++ b/addressbook/backend/ebook/e-book-query.c @@ -0,0 +1,524 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +#include <config.h> + +#include "e-book-query.h" +#include <e-util/e-sexp.h> + +#include <stdarg.h> +#include <string.h> + +typedef enum { + E_BOOK_QUERY_TYPE_AND, + E_BOOK_QUERY_TYPE_OR, + E_BOOK_QUERY_TYPE_NOT, + E_BOOK_QUERY_TYPE_FIELD_EXISTS, + E_BOOK_QUERY_TYPE_FIELD_TEST, + E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS +} EBookQueryType; + +struct EBookQuery { + EBookQueryType type; + int ref_count; + + union { + struct { + guint nqs; + EBookQuery **qs; + } andor; + + struct { + EBookQuery *q; + } not; + + struct { + EBookQueryTest test; + EContactField field; + char *value; + } field_test; + + struct { + EContactField field; + } exist; + + struct { + char *value; + } any_field_contains; + } query; +}; + +static EBookQuery * +conjoin (EBookQueryType type, int nqs, EBookQuery **qs, gboolean unref) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + int i; + + ret->type = type; + ret->query.andor.nqs = nqs; + ret->query.andor.qs = g_new (EBookQuery *, nqs); + for (i = 0; i < nqs; i++) { + ret->query.andor.qs[i] = qs[i]; + if (!unref) + e_book_query_ref (qs[i]); + } + + return ret; +} + +EBookQuery * +e_book_query_and (int nqs, EBookQuery **qs, gboolean unref) +{ + return conjoin (E_BOOK_QUERY_TYPE_AND, nqs, qs, unref); +} + +EBookQuery * +e_book_query_or (int nqs, EBookQuery **qs, gboolean unref) +{ + return conjoin (E_BOOK_QUERY_TYPE_OR, nqs, qs, unref); +} + +static EBookQuery * +conjoinv (EBookQueryType type, EBookQuery *q, va_list ap) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + GPtrArray *qs; + + qs = g_ptr_array_new (); + while (q) { + g_ptr_array_add (qs, q); + q = va_arg (ap, EBookQuery *); + } + va_end (ap); + + ret->type = type; + ret->query.andor.nqs = qs->len; + ret->query.andor.qs = (EBookQuery **)qs->pdata; + g_ptr_array_free (qs, FALSE); + + return ret; +} + +EBookQuery * +e_book_query_andv (EBookQuery *q, ...) +{ + va_list ap; + + va_start (ap, q); + return conjoinv (E_BOOK_QUERY_TYPE_AND, q, ap); +} + +EBookQuery * +e_book_query_orv (EBookQuery *q, ...) +{ + va_list ap; + + va_start (ap, q); + return conjoinv (E_BOOK_QUERY_TYPE_OR, q, ap); +} + +EBookQuery * +e_book_query_not (EBookQuery *q, gboolean unref) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + + ret->type = E_BOOK_QUERY_TYPE_NOT; + ret->query.not.q = q; + if (!unref) + e_book_query_ref (q); + + return ret; +} + +EBookQuery * +e_book_query_field_test (EContactField field, + EBookQueryTest test, + const char *value) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + + ret->type = E_BOOK_QUERY_TYPE_FIELD_TEST; + ret->query.field_test.field = field; + ret->query.field_test.test = test; + ret->query.field_test.value = g_strdup (value); + + return ret; +} + +EBookQuery * +e_book_query_field_exists (EContactField field) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + + ret->type = E_BOOK_QUERY_TYPE_FIELD_EXISTS; + ret->query.exist.field = field; + + return ret; +} + +EBookQuery * +e_book_query_any_field_contains (const char *value) +{ + EBookQuery *ret = g_new0 (EBookQuery, 1); + + ret->type = E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS; + ret->query.any_field_contains.value = g_strdup (value); + + return ret; +} + +void +e_book_query_unref (EBookQuery *q) +{ + int i; + + if (q->ref_count--) + return; + + switch (q->type) { + case E_BOOK_QUERY_TYPE_AND: + case E_BOOK_QUERY_TYPE_OR: + for (i = 0; i < q->query.andor.nqs; i++) + e_book_query_unref (q->query.andor.qs[i]); + g_free (q->query.andor.qs); + break; + + case E_BOOK_QUERY_TYPE_NOT: + e_book_query_unref (q->query.not.q); + break; + + case E_BOOK_QUERY_TYPE_FIELD_TEST: + g_free (q->query.field_test.value); + break; + + case E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS: + g_free (q->query.any_field_contains.value); + break; + + default: + break; + } + + g_free (q); +} + +void +e_book_query_ref (EBookQuery *q) +{ + q->ref_count++; +} + +static ESExpResult * +func_and(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + EBookQuery **qs; + + if (argc > 0) { + int i; + + qs = g_new0(EBookQuery*, argc); + + for (i = 0; i < argc; i ++) { + GList *list_head = *list; + if (!list_head) + break; + qs[i] = list_head->data; + *list = g_list_remove_link(*list, list_head); + g_list_free_1(list_head); + } + + *list = g_list_prepend(*list, + e_book_query_and (argc, qs, TRUE)); + + g_free (qs); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_or(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + EBookQuery **qs; + + if (argc > 0) { + int i; + + qs = g_new0(EBookQuery*, argc); + + for (i = 0; i < argc; i ++) { + GList *list_head = *list; + if (!list_head) + break; + qs[i] = list_head->data; + *list = g_list_remove_link(*list, list_head); + g_list_free_1(list_head); + } + + *list = g_list_prepend(*list, + e_book_query_or (argc, qs, TRUE)); + + g_free (qs); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_not(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + + /* just replace the head of the list with the NOT of it. */ + if (argc > 0) { + EBookQuery *term = (*list)->data; + (*list)->data = e_book_query_not (term, TRUE); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + + if (argc == 2 + && argv[0]->type == ESEXP_RES_STRING + && argv[1]->type == ESEXP_RES_STRING) { + char *propname = argv[0]->value.string; + char *str = argv[1]->value.string; + + if (!strcmp (propname, "x-evolution-any-field")) { + *list = g_list_prepend (*list, e_book_query_any_field_contains (str)); + } + else { + EContactField field = e_contact_field_id (propname); + + if (field) + *list = g_list_prepend (*list, e_book_query_field_test (field, + E_BOOK_QUERY_CONTAINS, + str)); + } + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + + if (argc == 2 + && argv[0]->type == ESEXP_RES_STRING + && argv[1]->type == ESEXP_RES_STRING) { + char *propname = argv[0]->value.string; + char *str = argv[1]->value.string; + EContactField field = e_contact_field_id (propname); + + if (field) + *list = g_list_prepend (*list, e_book_query_field_test (field, + E_BOOK_QUERY_IS, + str)); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + + if (argc == 2 + && argv[0]->type == ESEXP_RES_STRING + && argv[1]->type == ESEXP_RES_STRING) { + char *propname = argv[0]->value.string; + char *str = argv[1]->value.string; + EContactField field = e_contact_field_id (propname); + + if (field) + *list = g_list_prepend (*list, e_book_query_field_test (field, + E_BOOK_QUERY_BEGINS_WITH, + str)); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +static ESExpResult * +func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data) +{ + GList **list = data; + ESExpResult *r; + + if (argc == 2 + && argv[0]->type == ESEXP_RES_STRING + && argv[1]->type == ESEXP_RES_STRING) { + char *propname = argv[0]->value.string; + char *str = argv[1]->value.string; + EContactField field = e_contact_field_id (propname); + + if (field) + *list = g_list_prepend (*list, e_book_query_field_test (field, + E_BOOK_QUERY_ENDS_WITH, + str)); + } + + r = e_sexp_result_new(f, ESEXP_RES_BOOL); + r->value.bool = FALSE; + + return r; +} + +/* 'builtin' functions */ +static struct { + char *name; + ESExpFunc *func; + int type; /* set to 1 if a function can perform shortcut evaluation, or + doesn't execute everything, 0 otherwise */ +} symbols[] = { + { "and", func_and, 0 }, + { "or", func_or, 0 }, + { "not", func_not, 0 }, + { "contains", func_contains, 0 }, + { "is", func_is, 0 }, + { "beginswith", func_beginswith, 0 }, + { "endswith", func_endswith, 0 }, +}; + +EBookQuery* +e_book_query_from_string (const char *query_string) +{ + ESExp *sexp; + ESExpResult *r; + EBookQuery *retval; + GList *list = NULL; + int i; + + sexp = e_sexp_new(); + + for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) { + if (symbols[i].type == 1) { + e_sexp_add_ifunction(sexp, 0, symbols[i].name, + (ESExpIFunc *)symbols[i].func, &list); + } else { + e_sexp_add_function(sexp, 0, symbols[i].name, + symbols[i].func, &list); + } + } + + e_sexp_input_text(sexp, query_string, strlen(query_string)); + e_sexp_parse(sexp); + + r = e_sexp_eval(sexp); + + e_sexp_result_free(sexp, r); + e_sexp_unref (sexp); + + if (list) { + if (list->next) { + g_warning ("conversion to EBookQuery"); + retval = NULL; + g_list_foreach (list, (GFunc)e_book_query_unref, NULL); + } + else { + retval = list->data; + } + } + else { + g_warning ("conversion to EBookQuery failed"); + retval = NULL; + } + + g_list_free (list); + return retval; +} + +char* +e_book_query_to_string (EBookQuery *q) +{ + GString *str = g_string_new ("("); + int i; + char *s = NULL; + + switch (q->type) { + case E_BOOK_QUERY_TYPE_AND: + g_string_append (str, "and "); + for (i = 0; i < q->query.andor.nqs; i ++) { + s = e_book_query_to_string (q->query.andor.qs[i]); + g_string_append (str, s); + g_free (s); + g_string_append_c (str, ' '); + } + break; + case E_BOOK_QUERY_TYPE_OR: + g_string_append (str, "or "); + for (i = 0; i < q->query.andor.nqs; i ++) { + s = e_book_query_to_string (q->query.andor.qs[i]); + g_string_append (str, s); + g_free (s); + g_string_append_c (str, ' '); + } + break; + case E_BOOK_QUERY_TYPE_NOT: + s = e_book_query_to_string (q->query.not.q); + g_string_append_printf (str, "not %s", s); + g_free (s); + break; + case E_BOOK_QUERY_TYPE_FIELD_EXISTS: + g_string_append_printf (str, "exists \"%s\"", e_contact_field_name (q->query.exist.field)); + break; + case E_BOOK_QUERY_TYPE_FIELD_TEST: + switch (q->query.field_test.test) { + case E_BOOK_QUERY_IS: s = "is"; break; + case E_BOOK_QUERY_CONTAINS: s = "contains"; break; + case E_BOOK_QUERY_BEGINS_WITH: s = "beginswith"; break; + case E_BOOK_QUERY_ENDS_WITH: s = "endswith"; break; + default: + g_assert_not_reached(); + break; + } + + /* XXX need to escape q->query.field_test.value */ + g_string_append_printf (str, "%s \"%s\" \"%s\"", + s, + e_contact_field_name (q->query.field_test.field), + q->query.field_test.value); + break; + case E_BOOK_QUERY_TYPE_ANY_FIELD_CONTAINS: + g_string_append_printf (str, "contains \"x-evolution-any-field\" \"%s\"", q->query.any_field_contains.value); + break; + } + + + g_string_append (str, ")"); + + return g_string_free (str, FALSE); +} |