aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--addressbook/ChangeLog7
-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
4 files changed, 520 insertions, 0 deletions
diff --git a/addressbook/ChangeLog b/addressbook/ChangeLog
index 4cec0e3856..00816b50b8 100644
--- a/addressbook/ChangeLog
+++ b/addressbook/ChangeLog
@@ -1,3 +1,10 @@
+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.
+
2001-06-03 Chris Toshok <toshok@ximian.com>
* gui/component/addressbook.c (set_status_message): remove spew,
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__ */
+