/* -*- 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);
}