Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / addressbook / libedata-book / e-book-backend-sexp.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * pas-backend-card-sexp.c
4  * Copyright 1999, 2000, 2001, Ximian, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License, version 2, as published by the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  * 02110-1301, USA.
19  */
20
21 #include <string.h>
22 #include "libedataserver/e-sexp.h"
23 #include "libedataserver/e-data-server-util.h"
24 #include "e-book-backend-sexp.h"
25
26 static GObjectClass *parent_class;
27
28 typedef struct _SearchContext SearchContext;
29
30 struct _EBookBackendSExpPrivate {
31         ESExp *search_sexp;
32         SearchContext *search_context;
33 };
34
35 struct _SearchContext {
36         EContact *contact;
37 };
38
39 static gboolean
40 compare_im (EContact *contact, const char *str,
41             char *(*compare)(const char*, const char*),
42             EContactField im_field)
43 {
44         GList    *aims, *l;
45         gboolean  found_it = FALSE;
46
47         aims = e_contact_get (contact, im_field);
48
49         for (l = aims; l != NULL; l = l->next) {
50                 char *im = (char *) l->data;
51
52                 if (im && compare (im, str)) {
53                         found_it = TRUE;
54                         break;
55                 }
56         }
57         
58         g_list_foreach (aims, (GFunc)g_free, NULL);
59         g_list_free (aims);
60
61         return found_it;
62 }
63
64 static gboolean
65 compare_im_aim (EContact *contact, const char *str,
66                 char *(*compare)(const char*, const char*))
67 {
68         return compare_im (contact, str, compare, E_CONTACT_IM_AIM);
69 }
70
71 static gboolean
72 compare_im_msn (EContact *contact, const char *str,
73                 char *(*compare)(const char*, const char*))
74 {
75         return compare_im (contact, str, compare, E_CONTACT_IM_MSN);
76 }
77
78 static gboolean
79 compare_im_icq (EContact *contact, const char *str,
80                 char *(*compare)(const char*, const char*))
81 {
82         return compare_im (contact, str, compare, E_CONTACT_IM_ICQ);
83 }
84
85 static gboolean
86 compare_im_yahoo (EContact *contact, const char *str,
87                   char *(*compare)(const char*, const char*))
88 {
89         return compare_im (contact, str, compare, E_CONTACT_IM_YAHOO);
90 }
91
92 static gboolean
93 compare_im_gadugadu (EContact *contact, const char *str,
94                   char *(*compare)(const char*, const char*))
95 {
96         return compare_im (contact, str, compare, E_CONTACT_IM_GADUGADU);
97 }
98
99
100 static gboolean
101 compare_im_jabber (EContact *contact, const char *str,
102                    char *(*compare)(const char*, const char*))
103 {
104         return compare_im (contact, str, compare, E_CONTACT_IM_JABBER);
105 }
106
107 static gboolean
108 compare_im_groupwise (EContact *contact, const char *str,
109                       char *(*compare)(const char*, const char*))
110 {
111         return compare_im (contact, str, compare, E_CONTACT_IM_GROUPWISE);
112 }
113
114 static gboolean
115 compare_email (EContact *contact, const char *str,
116                char *(*compare)(const char*, const char*))
117 {
118         int i;
119
120         for (i = E_CONTACT_EMAIL_1; i <= E_CONTACT_EMAIL_4; i ++) {
121                 const char *email = e_contact_get_const (contact, i);
122
123                 if (email && compare(email, str))
124                         return TRUE;
125         }
126
127         return FALSE;
128 }
129
130 static gboolean
131 compare_phone (EContact *contact, const char *str,
132                char *(*compare)(const char*, const char*))
133 {
134         int i;
135         gboolean rv = FALSE;
136
137         for (i = E_CONTACT_FIRST_PHONE_ID; i <= E_CONTACT_LAST_PHONE_ID; i ++) {
138                 char *phone = e_contact_get (contact, i);
139
140                 rv = phone && compare(phone, str);
141                 g_free (phone);
142
143                 if (rv)
144                         break;
145         }
146
147         return rv;
148 }
149
150 static gboolean
151 compare_name (EContact *contact, const char *str,
152               char *(*compare)(const char*, const char*))
153 {
154         const char *name;
155
156         name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
157         if (name && compare (name, str))
158                 return TRUE;
159
160         name = e_contact_get_const (contact, E_CONTACT_FAMILY_NAME);
161         if (name && compare (name, str))
162                 return TRUE;
163         
164         name = e_contact_get_const (contact, E_CONTACT_GIVEN_NAME);
165         if (name && compare (name, str))
166                 return TRUE;
167
168         name = e_contact_get_const (contact, E_CONTACT_NICKNAME);
169         if (name && compare (name, str))
170                 return TRUE;
171
172         return FALSE;
173 }
174
175 static gboolean
176 compare_address (EContact *contact, const char *str,
177                  char *(*compare)(const char*, const char*))
178 {
179         
180         int i;
181         gboolean rv = FALSE;
182
183         for (i = E_CONTACT_FIRST_ADDRESS_ID; i <= E_CONTACT_LAST_ADDRESS_ID; i ++) {
184                 EContactAddress *address = e_contact_get (contact, i);
185                 if (address) {
186                         rv =  (address->po && compare(address->po, str)) ||
187                                 (address->street && compare(address->street, str)) ||
188                                 (address->ext && compare(address->ext, str)) ||
189                                 (address->locality && compare(address->locality, str)) ||
190                                 (address->region && compare(address->region, str)) || 
191                                 (address->code && compare(address->code, str)) ||
192                                 (address->country && compare(address->country, str));
193                         
194                         e_contact_address_free (address);
195                 
196                         if (rv)
197                                 break;
198                 }
199         }
200
201         return rv;
202         
203 }
204
205 static gboolean
206 compare_category (EContact *contact, const char *str,
207                   char *(*compare)(const char*, const char*))
208 {
209         GList *categories;
210         GList *iterator;
211         gboolean ret_val = FALSE;
212
213         categories = e_contact_get (contact, E_CONTACT_CATEGORY_LIST);
214
215         for (iterator = categories; iterator; iterator = iterator->next) {
216                 const char *category = iterator->data;
217
218                 if (compare(category, str)) {
219                         ret_val = TRUE;
220                         break;
221                 }
222         }
223
224         g_list_foreach (categories, (GFunc)g_free, NULL);
225         g_list_free (categories);
226
227         return ret_val;
228 }
229
230 static struct prop_info {
231         EContactField field_id;
232         const char *query_prop;
233 #define PROP_TYPE_NORMAL   0x01
234 #define PROP_TYPE_LIST     0x02
235         int prop_type;
236         gboolean (*list_compare)(EContact *contact, const char *str,
237                                  char *(*compare)(const char*, const char*));
238
239 } prop_info_table[] = {
240 #define NORMAL_PROP(f,q) {f, q, PROP_TYPE_NORMAL, NULL}
241 #define LIST_PROP(q,c) {0, q, PROP_TYPE_LIST, c}
242
243         /* query prop,   type,              list compare function */
244         NORMAL_PROP ( E_CONTACT_FILE_AS, "file_as" ),
245         NORMAL_PROP ( E_CONTACT_UID, "id" ),
246         LIST_PROP ( "full_name", compare_name), /* not really a list, but we need to compare both full and surname */
247         NORMAL_PROP ( E_CONTACT_HOMEPAGE_URL, "url"),
248         NORMAL_PROP ( E_CONTACT_BLOG_URL, "blog_url"),
249         NORMAL_PROP ( E_CONTACT_CALENDAR_URI, "calurl"),
250         NORMAL_PROP ( E_CONTACT_FREEBUSY_URL, "fburl"),
251         NORMAL_PROP ( E_CONTACT_ICS_CALENDAR, "icscalendar"),
252         NORMAL_PROP ( E_CONTACT_VIDEO_URL, "video_url"),
253
254         NORMAL_PROP ( E_CONTACT_MAILER, "mailer"),
255         NORMAL_PROP ( E_CONTACT_ORG, "org"),
256         NORMAL_PROP ( E_CONTACT_ORG_UNIT, "org_unit"),
257         NORMAL_PROP ( E_CONTACT_OFFICE, "office"),
258         NORMAL_PROP ( E_CONTACT_TITLE, "title"),
259         NORMAL_PROP ( E_CONTACT_ROLE, "role"),
260         NORMAL_PROP ( E_CONTACT_MANAGER, "manager"),
261         NORMAL_PROP ( E_CONTACT_ASSISTANT, "assistant"),
262         NORMAL_PROP ( E_CONTACT_NICKNAME, "nickname"),
263         NORMAL_PROP ( E_CONTACT_SPOUSE, "spouse" ),
264         NORMAL_PROP ( E_CONTACT_NOTE, "note"),
265         LIST_PROP ( "im_aim",    compare_im_aim ),
266         LIST_PROP ( "im_msn",    compare_im_msn ),
267         LIST_PROP ( "im_icq",    compare_im_icq ),
268         LIST_PROP ( "im_jabber", compare_im_jabber ),
269         LIST_PROP ( "im_yahoo",  compare_im_yahoo ),
270         LIST_PROP ( "im_gadugadu",  compare_im_gadugadu ),
271         LIST_PROP ( "im_groupwise", compare_im_groupwise ),
272         LIST_PROP ( "email",     compare_email ),
273         LIST_PROP ( "phone",     compare_phone ),
274         LIST_PROP ( "address",   compare_address ),
275         LIST_PROP ( "category_list",  compare_category ),
276 };
277 static int num_prop_infos = sizeof(prop_info_table) / sizeof(prop_info_table[0]);
278
279 static ESExpResult *
280 entry_compare(SearchContext *ctx, struct _ESExp *f,
281               int argc, struct _ESExpResult **argv,
282               char *(*compare)(const char*, const char*))
283 {
284         ESExpResult *r;
285         int truth = FALSE;
286
287         if (argc == 2
288             && argv[0]->type == ESEXP_RES_STRING
289             && argv[1]->type == ESEXP_RES_STRING) {
290                 char *propname;
291                 struct prop_info *info = NULL;
292                 int i;
293                 gboolean any_field;
294
295                 propname = argv[0]->value.string;
296
297                 any_field = !strcmp(propname, "x-evolution-any-field");
298                 for (i = 0; i < num_prop_infos; i ++) {
299                         if (any_field
300                             || !strcmp (prop_info_table[i].query_prop, propname)) {
301                                 info = &prop_info_table[i];
302                 
303                                 if (any_field && info->field_id == E_CONTACT_UID) {
304                                         /* We need to skip UID from any field contains search
305                                          * any-field search should be supported for the 
306                                          * visible fields only.
307                                          */
308                                         truth = FALSE;
309                                 }
310                                 else if (info->prop_type == PROP_TYPE_NORMAL) {
311                                         const char *prop = NULL;
312                                         /* straight string property matches */
313                                         
314                                         prop = e_contact_get_const (ctx->contact, info->field_id);
315
316                                         if (prop && compare(prop, argv[1]->value.string)) {
317                                                 truth = TRUE;
318                                         }
319                                         if ((!prop) && compare("", argv[1]->value.string)) {
320                                                 truth = TRUE;
321                                         }
322                                 }
323                                 else if (info->prop_type == PROP_TYPE_LIST) {
324                                         /* the special searches that match any of the list elements */
325                                         truth = info->list_compare (ctx->contact, argv[1]->value.string, compare);
326                                 }
327
328                                 /* if we're looking at all fields and find a match,
329                                    or if we're just looking at this one field,
330                                    break. */
331                                 if ((any_field && truth)
332                                     || !any_field)
333                                         break;
334                         }
335                 }
336                 
337         }
338         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
339         r->value.bool = truth;
340
341         return r;
342 }
343
344 static ESExpResult *
345 func_contains(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
346 {
347         SearchContext *ctx = data;
348
349         return entry_compare (ctx, f, argc, argv, (char *(*)(const char*, const char*)) e_util_utf8_strstrcase);
350 }
351
352 static char *
353 is_helper (const char *s1, const char *s2)
354 {
355         if (!e_util_utf8_strcasecmp (s1, s2))
356                 return (char*)s1;
357         else
358                 return NULL;
359 }
360
361 static ESExpResult *
362 func_is(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
363 {
364         SearchContext *ctx = data;
365
366         return entry_compare (ctx, f, argc, argv, is_helper);
367 }
368
369 static char *
370 endswith_helper (const char *s1, const char *s2)
371 {
372         char *p;
373         if ((p = (char*) e_util_utf8_strstrcase(s1, s2))
374             && (strlen(p) == strlen(s2)))
375                 return p;
376         else
377                 return NULL;
378 }
379
380 static ESExpResult *
381 func_endswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
382 {
383         SearchContext *ctx = data;
384
385         return entry_compare (ctx, f, argc, argv, endswith_helper);
386 }
387
388 static char *
389 beginswith_helper (const char *s1, const char *s2)
390 {
391         char *p;
392         if ((p = (char*) e_util_utf8_strstrcase(s1, s2))
393             && (p == s1))
394                 return p;
395         else
396                 return NULL;
397 }
398
399 static ESExpResult *
400 func_beginswith(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
401 {
402         SearchContext *ctx = data;
403
404         return entry_compare (ctx, f, argc, argv, beginswith_helper);
405 }
406
407 static ESExpResult *
408 func_exists(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
409 {
410         SearchContext *ctx = data;
411         ESExpResult *r;
412         int truth = FALSE;
413
414         if (argc == 1
415             && argv[0]->type == ESEXP_RES_STRING) {
416                 char *propname;
417                 struct prop_info *info = NULL;
418                 int i;
419
420                 propname = argv[0]->value.string;
421
422                 for (i = 0; i < num_prop_infos; i ++) {
423                         if (!strcmp (prop_info_table[i].query_prop, propname)) {
424                                 info = &prop_info_table[i];
425                                 
426                                 if (info->prop_type == PROP_TYPE_NORMAL) {
427                                         const char *prop = NULL;
428                                         /* searches where the query's property
429                                            maps directly to an ecard property */
430                                         
431                                         prop = e_contact_get_const (ctx->contact, info->field_id);
432
433                                         if (prop && *prop)
434                                                 truth = TRUE;
435                                 }
436                                 else if (info->prop_type == PROP_TYPE_LIST) {
437                                 /* the special searches that match any of the list elements */
438                                         truth = info->list_compare (ctx->contact, "", (char *(*)(const char*, const char*)) e_util_utf8_strstrcase);
439                                 }
440
441                                 break;
442                         }
443                 }
444                 
445         }
446         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
447         r->value.bool = truth;
448
449         return r;
450 }
451
452 static ESExpResult *
453 func_exists_vcard(struct _ESExp *f, int argc, struct _ESExpResult **argv, void *data)
454 {
455         SearchContext *ctx = data;
456         ESExpResult *r;
457         int truth = FALSE;
458
459         if (argc == 1 && argv[0]->type == ESEXP_RES_STRING) {
460                 const char *attr_name;
461                 EVCardAttribute *attr;
462                 GList *values;
463                 char *s;
464
465                 attr_name = argv[0]->value.string;
466                 attr = e_vcard_get_attribute (E_VCARD (ctx->contact), attr_name);
467                 if (attr) {
468                         values = e_vcard_attribute_get_values (attr);
469                         if (g_list_length (values) > 0) {
470                                 s = values->data;
471                                 if (s[0] != '\0') {
472                                         truth = TRUE;
473                                 }
474                         }
475                 }
476         }
477         
478         r = e_sexp_result_new(f, ESEXP_RES_BOOL);
479         r->value.bool = truth;
480
481         return r;
482 }
483
484 /* 'builtin' functions */
485 static struct {
486         char *name;
487         ESExpFunc *func;
488         int type;               /* set to 1 if a function can perform shortcut evaluation, or
489                                    doesn't execute everything, 0 otherwise */
490 } symbols[] = {
491         { "contains", func_contains, 0 },
492         { "is", func_is, 0 },
493         { "beginswith", func_beginswith, 0 },
494         { "endswith", func_endswith, 0 },
495         { "exists", func_exists, 0 },
496         { "exists_vcard", func_exists_vcard, 0 },
497 };
498
499 /**
500  * e_book_backend_sexp_match_contact:
501  * @sexp: an #EBookBackendSExp
502  * @contact: an #EContact
503  *
504  * Checks if @contact matches @sexp.
505  *
506  * Return value: %TRUE if the contact matches, %FALSE otherwise.
507  **/
508 gboolean
509 e_book_backend_sexp_match_contact (EBookBackendSExp *sexp, EContact *contact)
510 {
511         ESExpResult *r;
512         gboolean retval;
513
514         if (!contact) {
515                 g_warning ("null EContact passed to e_book_backend_sexp_match_contact");
516                 return FALSE;
517         }
518
519         sexp->priv->search_context->contact = g_object_ref (contact);
520
521         r = e_sexp_eval(sexp->priv->search_sexp);
522
523         retval = (r && r->type == ESEXP_RES_BOOL && r->value.bool);
524
525         g_object_unref(sexp->priv->search_context->contact);
526
527         e_sexp_result_free(sexp->priv->search_sexp, r);
528
529         return retval;
530 }
531
532 /**
533  * e_book_backend_sexp_match_vcard:
534  * @sexp: an #EBookBackendSExp
535  * @vcard: a VCard string
536  *
537  * Checks if @vcard matches @sexp.
538  *
539  * Return value: %TRUE if the VCard matches, %FALSE otherwise.
540  **/
541 gboolean
542 e_book_backend_sexp_match_vcard (EBookBackendSExp *sexp, const char *vcard)
543 {
544         EContact *contact;
545         gboolean retval;
546
547         contact = e_contact_new_from_vcard (vcard);
548
549         retval = e_book_backend_sexp_match_contact (sexp, contact);
550
551         g_object_unref(contact);
552
553         return retval;
554 }
555
556 \f
557
558 /**
559  * e_book_backend_sexp_new:
560  * @text: an s-expression to parse
561  *
562  * Creates a new #EBookBackendSExp from @text.
563  *
564  * Return value: A new #EBookBackendSExp.
565  **/
566 EBookBackendSExp *
567 e_book_backend_sexp_new (const char *text)
568 {
569         EBookBackendSExp *sexp = g_object_new (E_TYPE_BACKEND_SEXP, NULL);
570         int esexp_error;
571         int i;
572
573         sexp->priv->search_sexp = e_sexp_new();
574
575         for(i=0;i<sizeof(symbols)/sizeof(symbols[0]);i++) {
576                 if (symbols[i].type == 1) {
577                         e_sexp_add_ifunction(sexp->priv->search_sexp, 0, symbols[i].name,
578                                              (ESExpIFunc *)symbols[i].func, sexp->priv->search_context);
579                 }
580                 else {
581                         e_sexp_add_function(sexp->priv->search_sexp, 0, symbols[i].name,
582                                             symbols[i].func, sexp->priv->search_context);
583                 }
584         }
585
586         e_sexp_input_text(sexp->priv->search_sexp, text, strlen(text));
587         esexp_error = e_sexp_parse(sexp->priv->search_sexp);
588
589         if (esexp_error == -1) {
590                 g_object_unref (sexp);
591                 sexp = NULL;
592         }
593
594         return sexp;
595 }
596
597 static void
598 e_book_backend_sexp_dispose (GObject *object)
599 {
600         EBookBackendSExp *sexp = E_BOOK_BACKEND_SEXP (object);
601
602         if (sexp->priv) {
603                 e_sexp_unref(sexp->priv->search_sexp);
604
605                 g_free (sexp->priv->search_context);
606                 g_free (sexp->priv);
607                 sexp->priv = NULL;
608         }
609
610         if (G_OBJECT_CLASS (parent_class)->dispose)
611                 G_OBJECT_CLASS (parent_class)->dispose (object);
612 }
613
614 static void
615 e_book_backend_sexp_class_init (EBookBackendSExpClass *klass)
616 {
617         GObjectClass  *object_class = G_OBJECT_CLASS (klass);
618
619         parent_class = g_type_class_peek_parent (klass);
620
621         /* Set the virtual methods. */
622
623         object_class->dispose = e_book_backend_sexp_dispose;
624 }
625
626 static void
627 e_book_backend_sexp_init (EBookBackendSExp *sexp)
628 {
629         EBookBackendSExpPrivate *priv;
630
631         priv             = g_new0 (EBookBackendSExpPrivate, 1);
632
633         sexp->priv = priv;
634         priv->search_context = g_new (SearchContext, 1);
635 }
636
637 /**
638  * e_book_backend_sexp_get_type:
639  */
640 GType
641 e_book_backend_sexp_get_type (void)
642 {
643         static GType type = 0;
644
645         if (! type) {
646                 GTypeInfo info = {
647                         sizeof (EBookBackendSExpClass),
648                         NULL, /* base_class_init */
649                         NULL, /* base_class_finalize */
650                         (GClassInitFunc)  e_book_backend_sexp_class_init,
651                         NULL, /* class_finalize */
652                         NULL, /* class_data */
653                         sizeof (EBookBackendSExp),
654                         0,    /* n_preallocs */
655                         (GInstanceInitFunc) e_book_backend_sexp_init
656                 };
657
658                 type = g_type_register_static (G_TYPE_OBJECT, "EBookBackendSExp", &info, 0);
659         }
660
661         return type;
662 }