aboutsummaryrefslogtreecommitdiffstats
path: root/addressbook/backend/ebook
diff options
context:
space:
mode:
authorJon Trowbridge <trow@ximian.com>2001-06-05 06:20:27 +0800
committerJon Trowbridge <trow@src.gnome.org>2001-06-05 06:20:27 +0800
commiteacf872e7848a120259e7bd3316cdf94319ef696 (patch)
tree0da40b332589c113dd02669e3571e168c7e72076 /addressbook/backend/ebook
parent342de51c7d93f94c9b19444547cac2a5f6441c2f (diff)
downloadgsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar.gz
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar.bz2
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar.lz
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar.xz
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.tar.zst
gsoc2013-evolution-eacf872e7848a120259e7bd3316cdf94319ef696.zip
Added. Code for testing if two ECards appear to pertain to the same
2001-06-04 Jon Trowbridge <trow@ximian.com> * backend/ebook/e-card-compare.c: Added. Code for testing if two ECards appear to pertain to the same contact (using loose matching rules, as opposed to requiring exact equality) and to query the addressbook for the "best match" to ECard. svn path=/trunk/; revision=10114
Diffstat (limited to 'addressbook/backend/ebook')
-rw-r--r--addressbook/backend/ebook/Makefile.am2
-rw-r--r--addressbook/backend/ebook/e-card-compare.c455
-rw-r--r--addressbook/backend/ebook/e-card-compare.h56
3 files changed, 513 insertions, 0 deletions
diff --git a/addressbook/backend/ebook/Makefile.am b/addressbook/backend/ebook/Makefile.am
index 86a97a1e56..872d7128e1 100644
--- a/addressbook/backend/ebook/Makefile.am
+++ b/addressbook/backend/ebook/Makefile.am
@@ -47,6 +47,7 @@ libebook_la_SOURCES = \
e-card-cursor.c \
e-card-simple.c \
e-card.c \
+ e-card-compare.c \
e-destination.c
libebookincludedir = $(includedir)/evolution/ebook
@@ -62,6 +63,7 @@ libebookinclude_HEADERS = \
e-card-simple.h \
e-card-types.h \
e-card.h \
+ e-card-compare.h \
e-destination.h
diff --git a/addressbook/backend/ebook/e-card-compare.c b/addressbook/backend/ebook/e-card-compare.c
new file mode 100644
index 0000000000..fc80d5e6c6
--- /dev/null
+++ b/addressbook/backend/ebook/e-card-compare.c
@@ -0,0 +1,455 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * e-card-compare.c
+ *
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Developed by Jon Trowbridge <trow@ximian.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ */
+
+#include <config.h>
+#include <ctype.h>
+#include <gal/unicode/gunicode.h>
+#include "e-book-util.h"
+#include "e-card-compare.h"
+
+/* This is an "optimistic" combiner: the best of the two outcomes is
+ selected. */
+static ECardMatchType
+combine_comparisons (ECardMatchType prev,
+ ECardMatchType new_info)
+{
+ if (new_info == E_CARD_MATCH_NOT_APPLICABLE)
+ return prev;
+ return (ECardMatchType) MAX ((gint) prev, (gint) new_info);
+}
+
+
+/*** Name comparisons ***/
+
+/* This *so* doesn't belong here... at least not implemented in a
+ sucky way like this. But by getting it in here now, I can fix it
+ up w/o adding a new feature when we are in feature freeze. :-) */
+
+/* This is very Anglocentric. */
+static gchar *name_synonyms[][2] = {
+ { "jon", "john" }, /* Ah, the hacker's perogative */
+ { "joseph", "joe" },
+ { "robert", "bob" },
+ { "richard", "dick" },
+ { "william", "bill" },
+ { "anthony", "tony" },
+ { "michael", "mike" },
+ { "eric", "erik" },
+ { "elizabeth", "liz" },
+ { "jeff", "geoff" },
+ { "jeff", "geoffrey" },
+ { "jim", "james" },
+ { "abigal", "abby" },
+ { "amanda", "amy" },
+ { "amanda", "manda" },
+ { "jennifer", "jenny" },
+ { "rebecca", "becca" },
+ { "rebecca", "becky" },
+ { "anderson", "andersen" },
+ /* We could go on and on... */
+ { NULL, NULL }
+};
+
+static gboolean
+name_fragment_match (const gchar *a, const gchar *b)
+{
+ gint i, len_a, len_b;
+
+ /* This will cause "Chris" and "Christopher" to match. */
+ len_a = g_utf8_strlen (a, -1);
+ len_b = g_utf8_strlen (b, -1);
+ if (!g_utf8_strncasecmp (a, b, MIN (len_a, len_b)))
+ return TRUE;
+
+ /* Check for nicknames. Yes, the linear search blows. */
+ for (i=0; name_synonyms[i][0]; ++i) {
+
+ if (!g_utf8_strcasecmp (name_synonyms[i][0], a)
+ && !g_utf8_strcasecmp (name_synonyms[i][1], b))
+ return TRUE;
+
+ if (!g_utf8_strcasecmp (name_synonyms[i][0], b)
+ && !g_utf8_strcasecmp (name_synonyms[i][1], a))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+ECardMatchType
+e_card_compare_name (ECard *card1, ECard *card2)
+{
+ ECardName *a, *b;
+ gint matches=0, possible=0;
+
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ a = card1->name;
+ b = card2->name;
+
+ if (a == NULL || b == NULL)
+ return E_CARD_MATCH_NOT_APPLICABLE;
+
+ if (a->given && b->given) {
+ ++possible;
+ if (name_fragment_match (a->given, b->given))
+ ++matches;
+ }
+
+ if (a->additional && b->additional) {
+ ++possible;
+ if (name_fragment_match (a->additional, b->additional))
+ ++matches;
+ }
+
+ if (a->family && b->family) {
+ ++possible;
+ if (name_fragment_match (a->family, b->family))
+ ++matches;
+ }
+
+ /* Now look at the # of matches and try to intelligently map
+ an E_CARD_MATCH_* type to it. */
+
+ if (possible == 0)
+ return E_CARD_MATCH_NOT_APPLICABLE;
+ if (matches == 0)
+ return E_CARD_MATCH_NONE;
+
+ if (matches == possible) {
+ return possible > 1 ? E_CARD_MATCH_EXACT : E_CARD_MATCH_PARTIAL;
+ } else if (matches == possible-1)
+ return E_CARD_MATCH_PARTIAL;
+ else
+ return E_CARD_MATCH_VAGUE;
+}
+
+
+/*** Nickname Comparisons ***/
+
+ECardMatchType
+e_card_compare_nickname (ECard *card1, ECard *card2)
+{
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ return E_CARD_MATCH_NOT_APPLICABLE;
+}
+
+
+
+/*** E-mail Comparisons ***/
+
+static gboolean
+match_email_username (const gchar *addr1, const gchar *addr2)
+{
+ gint c1, c2;
+ if (addr1 == NULL || addr2 == NULL)
+ return FALSE;
+
+ while (*addr1 && *addr2 && *addr1 != '@' && *addr2 != '@') {
+ c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
+ c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
+ if (c1 != c2)
+ return FALSE;
+ ++addr1;
+ ++addr2;
+ }
+
+ return *addr1 == *addr2;
+}
+
+static gboolean
+match_email_hostname (const gchar *addr1, const gchar *addr2)
+{
+ gint c1, c2;
+ gboolean seen_at1, seen_at2;
+ if (addr1 == NULL || addr2 == NULL)
+ return FALSE;
+
+ /* Walk to the end of each string. */
+ seen_at1 = FALSE;
+ if (*addr1) {
+ while (*addr1) {
+ if (*addr1 == '@')
+ seen_at1 = TRUE;
+ ++addr1;
+ }
+ --addr1;
+ }
+
+ seen_at2 = FALSE;
+ if (*addr2) {
+ while (*addr2) {
+ if (*addr2 == '@')
+ seen_at2 = TRUE;
+ ++addr2;
+ }
+ --addr2;
+ }
+
+ if (!seen_at1 && !seen_at2)
+ return TRUE;
+ if (!seen_at1 || !seen_at2)
+ return FALSE;
+
+ while (*addr1 != '@' && *addr2 != '@') {
+ c1 = isupper (*addr1) ? tolower (*addr1) : *addr1;
+ c2 = isupper (*addr2) ? tolower (*addr2) : *addr2;
+ if (c1 != c2)
+ return FALSE;
+ --addr1;
+ --addr2;
+ }
+
+ /* This will match bob@foo.ximian.com and bob@ximian.com */
+ return *addr1 == '.' || *addr2 == '.';
+}
+
+static ECardMatchType
+compare_email_addresses (const gchar *addr1, const gchar *addr2)
+{
+ if (addr1 == NULL || addr2 == NULL)
+ return E_CARD_MATCH_NOT_APPLICABLE;
+
+ if (match_email_username (addr1, addr2))
+ return match_email_hostname (addr1, addr2) ? E_CARD_MATCH_EXACT : E_CARD_MATCH_PARTIAL;
+
+ return E_CARD_MATCH_NONE;
+}
+
+ECardMatchType
+e_card_compare_email (ECard *card1, ECard *card2)
+{
+ EIterator *i1, *i2;
+ ECardMatchType match = E_CARD_MATCH_NOT_APPLICABLE;
+
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ if (card1->email == NULL || card2->email == NULL)
+ return E_CARD_MATCH_NOT_APPLICABLE;
+
+ i1 = e_list_get_iterator (card1->email);
+ i2 = e_list_get_iterator (card2->email);
+
+ /* Do pairwise-comparisons on all of the e-mail addresses. If
+ we find an exact match, there is no reason to keep
+ checking. */
+ e_iterator_reset (i1);
+ while (e_iterator_is_valid (i1) && match != E_CARD_MATCH_EXACT) {
+ const gchar *addr1 = (const gchar *) e_iterator_get (i1);
+
+ e_iterator_reset (i2);
+ while (e_iterator_is_valid (i2) && match != E_CARD_MATCH_EXACT) {
+ const gchar *addr2 = (const gchar *) e_iterator_get (i2);
+
+ match = combine_comparisons (match, compare_email_addresses (addr1, addr2));
+
+ e_iterator_next (i2);
+ }
+
+ e_iterator_next (i1);
+ }
+
+ gtk_object_unref (GTK_OBJECT (i1));
+ gtk_object_unref (GTK_OBJECT (i2));
+
+ return match;
+}
+
+ECardMatchType
+e_card_compare_address (ECard *card1, ECard *card2)
+{
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ /* Unimplemented */
+
+ return E_CARD_MATCH_NOT_APPLICABLE;
+}
+
+ECardMatchType
+e_card_compare_telephone (ECard *card1, ECard *card2)
+{
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ /* Unimplemented */
+
+ return E_CARD_MATCH_NOT_APPLICABLE;
+}
+
+ECardMatchType
+e_card_compare (ECard *card1, ECard *card2)
+{
+ ECardMatchType result;
+
+ g_return_val_if_fail (card1 && E_IS_CARD (card1), E_CARD_MATCH_NOT_APPLICABLE);
+ g_return_val_if_fail (card2 && E_IS_CARD (card2), E_CARD_MATCH_NOT_APPLICABLE);
+
+ result = E_CARD_MATCH_NONE;
+ result = combine_comparisons (result, e_card_compare_name (card1, card2));
+ result = combine_comparisons (result, e_card_compare_nickname (card1, card2));
+ result = combine_comparisons (result, e_card_compare_email (card1, card2));
+ result = combine_comparisons (result, e_card_compare_address (card1, card2));
+ result = combine_comparisons (result, e_card_compare_telephone (card1, card2));
+
+ return result;
+}
+
+typedef struct _MatchSearchInfo MatchSearchInfo;
+struct _MatchSearchInfo {
+ ECard *card;
+ ECardMatchQueryCallback cb;
+ gpointer closure;
+};
+
+static void
+match_search_info_free (MatchSearchInfo *info)
+{
+ if (info) {
+ gtk_object_unref (GTK_OBJECT (info->card));
+ g_free (info);
+ }
+}
+
+static void
+simple_query_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure)
+{
+ MatchSearchInfo *info = (MatchSearchInfo *) closure;
+ ECardMatchType best_match = E_CARD_MATCH_NONE;
+ ECard *best_card = NULL;
+ const GList *i;
+
+ if (status != E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS) {
+ info->cb (info->card, NULL, E_CARD_MATCH_NONE, info->closure);
+ match_search_info_free (info);
+ return;
+ }
+
+ for (i = cards; i != NULL; i = g_list_next (i)) {
+ ECard *this_card = E_CARD (i->data);
+ ECardMatchType this_match = e_card_compare (info->card, this_card);
+ if ((gint)this_match > (gint)best_match) {
+ best_match = this_match;
+ best_card = this_card;
+ }
+ }
+
+ info->cb (info->card, best_card, best_match, info->closure);
+ match_search_info_free (info);
+}
+
+#define MAX_QUERY_PARTS 10
+static void
+use_common_book_cb (EBook *book, gpointer closure)
+{
+ MatchSearchInfo *info = (MatchSearchInfo *) closure;
+ ECard *card = info->card;
+ gchar *query_parts[MAX_QUERY_PARTS];
+ gint p=0;
+ gchar *query, *qj;
+
+ if (book == NULL) {
+ info->cb (info->card, NULL, E_CARD_MATCH_NONE, info->closure);
+ match_search_info_free (info);
+ return;
+ }
+
+ if (card->nickname)
+ query_parts[p++] = g_strdup_printf ("(beginswith \"nickname\" \"%s\")", card->nickname);
+
+
+ if (card->name->given && strlen (card->name->given) > 1)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", card->name->given);
+
+ if (card->name->additional && strlen (card->name->additional) > 1)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", card->name->additional);
+
+ if (card->name->family && strlen (card->name->family) > 1)
+ query_parts[p++] = g_strdup_printf ("(contains \"full_name\" \"%s\")", card->name->family);
+
+
+ if (card->email) {
+ EIterator *iter = e_list_get_iterator (card->email);
+ while (e_iterator_is_valid (iter) && p < MAX_QUERY_PARTS) {
+ gchar *addr = g_strdup (e_iterator_get (iter));
+ if (addr) {
+ gchar *s = addr;
+ while (*s) {
+ if (*s == '@') {
+ *s = '\0';
+ break;
+ }
+ ++s;
+ }
+ query_parts[p++] = g_strdup_printf ("beginswith \"email\" \"%s\")", addr);
+ g_free (addr);
+ }
+ e_iterator_next (iter);
+ }
+ }
+
+
+
+ /* Build up our full query from the parts. */
+
+ qj = g_strjoinv (" ", query_parts);
+ if (p > 0) {
+ query = g_strdup_printf ("(or %s)", qj);
+ } else {
+ query = qj;
+ qj = NULL;
+ }
+
+ e_book_simple_query (book, query, simple_query_cb, info);
+
+ for (p=0; query_parts[p]; ++p)
+ g_free (query_parts[p]);
+ g_free (qj);
+ g_free (query);
+}
+
+void
+e_card_locate_match (ECard *card, ECardMatchQueryCallback cb, gpointer closure)
+{
+ MatchSearchInfo *info;
+
+ g_return_if_fail (card && E_IS_CARD (card));
+ g_return_if_fail (cb != NULL);
+
+ info = g_new (MatchSearchInfo, 1);
+ info->card = card;
+ gtk_object_unref (GTK_OBJECT (card));
+ info->cb = cb;
+ info->closure = closure;
+
+ e_book_use_local_address_book (use_common_book_cb, info);
+}
+
diff --git a/addressbook/backend/ebook/e-card-compare.h b/addressbook/backend/ebook/e-card-compare.h
new file mode 100644
index 0000000000..7c55dabeab
--- /dev/null
+++ b/addressbook/backend/ebook/e-card-compare.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * e-card-compare.h
+ *
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Developed by Jon Trowbridge <trow@ximian.com>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA.
+ */
+
+#ifndef __E_CARD_COMPARE_H__
+#define __E_CARD_COMPARE_H__
+
+#include "e-card.h"
+
+typedef enum {
+ E_CARD_MATCH_NOT_APPLICABLE = 0,
+ E_CARD_MATCH_NONE = 1,
+ E_CARD_MATCH_VAGUE = 2,
+ E_CARD_MATCH_PARTIAL = 3,
+ E_CARD_MATCH_EXACT = 4
+} ECardMatchType;
+
+typedef void (*ECardMatchQueryCallback) (ECard *card, ECard *match, ECardMatchType type, gpointer closure);
+
+ECardMatchType e_card_compare_name (ECard *card1, ECard *card2);
+ECardMatchType e_card_compare_nickname (ECard *card1, ECard *card2);
+ECardMatchType e_card_compare_email (ECard *card1, ECard *card2);
+ECardMatchType e_card_compare_address (ECard *card1, ECard *card2);
+ECardMatchType e_card_compare_telephone (ECard *card1, ECard *card2);
+
+ECardMatchType e_card_compare (ECard *card1, ECard *card2);
+
+void e_card_locate_match (ECard *card, ECardMatchQueryCallback cb, gpointer closure);
+
+
+
+#endif /* __E_CARD_COMPARE_H__ */
+