[evolution-data-server/openismus-phonenumber-work: 14/14] Permit smart comparison of phone numbers



commit 9aaf5e7c4d2b993b321e73f09020ff8d0928d0d6
Author: Mathias Hasselmann <mathias openismus com>
Date:   Thu Dec 6 23:53:10 2012 +0100

    Permit smart comparison of phone numbers
    
    Introduces new e-phone-util functions for comparing phone
    numbers and uses them in e-book-backend-sexp.c to implement
    fallback routines for "eqphone" and friends.

 addressbook/libedata-book/e-book-backend-sexp.c    |   75 +++++++++++-
 .../libedataserver/libedataserver-sections.txt     |    2 +
 libedataserver/e-phone-utils.cpp                   |   83 ++++++++++++-
 libedataserver/e-phone-utils.h                     |   46 +++++++
 tests/libebook/data/vcards/custom-1.vcf            |    2 +-
 tests/libedataserver/e-phone-utils-test.c          |  133 +++++++++++++++++++-
 6 files changed, 330 insertions(+), 11 deletions(-)
---
diff --git a/addressbook/libedata-book/e-book-backend-sexp.c b/addressbook/libedata-book/e-book-backend-sexp.c
index ef7ae97..3fac3b0 100644
--- a/addressbook/libedata-book/e-book-backend-sexp.c
+++ b/addressbook/libedata-book/e-book-backend-sexp.c
@@ -18,10 +18,12 @@
  * 02110-1301, USA.
  */
 
-#include <string.h>
-
 #include "e-book-backend-sexp.h"
 
+#include <libedataserver/libedataserver.h>
+
+#include <string.h>
+
 #define E_BOOK_BACKEND_SEXP_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
 	((obj), E_TYPE_BOOK_BACKEND_SEXP, EBookBackendSExpPrivate))
@@ -744,6 +746,72 @@ func_beginswith (struct _ESExp *f,
 }
 
 static gboolean
+eqphone_helper (const gchar *ps1,
+                const gchar *ps2,
+                EPhoneNumberMatch required_match)
+{
+	const EPhoneNumberMatch actual_match =
+		e_phone_number_compare_strings (ps1, ps2, NULL);
+
+	return actual_match >= E_PHONE_NUMBER_MATCH_EXACT
+		&& actual_match <= required_match;
+}
+
+static gboolean
+eqphone_exact_helper (const gchar *ps1,
+                      const gchar *ps2)
+{
+	return eqphone_helper (ps1, ps2, E_PHONE_NUMBER_MATCH_EXACT);
+}
+
+static gboolean
+eqphone_national_helper (const gchar *ps1,
+                         const gchar *ps2)
+{
+	return eqphone_helper (ps1, ps2, E_PHONE_NUMBER_MATCH_NATIONAL);
+}
+
+static gboolean
+eqphone_short_helper (const gchar *ps1,
+                      const gchar *ps2)
+{
+	return eqphone_helper (ps1, ps2, E_PHONE_NUMBER_MATCH_SHORT);
+}
+
+static ESExpResult *
+func_eqphone (struct _ESExp *f,
+              gint argc,
+              struct _ESExpResult **argv,
+              gpointer data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, eqphone_exact_helper);
+}
+
+static ESExpResult *
+func_eqphone_national (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, eqphone_national_helper);
+}
+
+static ESExpResult *
+func_eqphone_short (struct _ESExp *f,
+                    gint argc,
+                    struct _ESExpResult **argv,
+                    gpointer data)
+{
+	SearchContext *ctx = data;
+
+	return entry_compare (ctx, f, argc, argv, eqphone_short_helper);
+}
+
+static gboolean
 exists_helper (const gchar *ps1,
                const gchar *ps2)
 {
@@ -894,6 +962,9 @@ static struct {
 	{ "is", func_is, 0 },
 	{ "beginswith", func_beginswith, 0 },
 	{ "endswith", func_endswith, 0 },
+	{ "eqphone", func_eqphone, 0 },
+	{ "eqphone_national", func_eqphone_national, 0 },
+	{ "eqphone_short", func_eqphone_short, 0 },
 	{ "exists", func_exists, 0 },
 	{ "exists_vcard", func_exists_vcard, 0 },
 };
diff --git a/docs/reference/libedataserver/libedataserver-sections.txt b/docs/reference/libedataserver/libedataserver-sections.txt
index e6382e7..c395cd5 100644
--- a/docs/reference/libedataserver/libedataserver-sections.txt
+++ b/docs/reference/libedataserver/libedataserver-sections.txt
@@ -221,6 +221,8 @@ EPhoneNumberError
 EPhoneNumberFormat
 e_phone_number_from_string
 e_phone_number_to_string
+e_phone_number_compare
+e_phone_number_compare_strings
 e_phone_number_copy
 e_phone_number_free
 <SUBSECTION Standard>
diff --git a/libedataserver/e-phone-utils.cpp b/libedataserver/e-phone-utils.cpp
index 802739b..d2ad7b6 100644
--- a/libedataserver/e-phone-utils.cpp
+++ b/libedataserver/e-phone-utils.cpp
@@ -46,7 +46,7 @@ using i18n::phonenumbers::PhoneNumberUtil;
 
 struct _EPhoneNumber {
 #ifdef ENABLE_PHONENUMBER
-	i18n::phonenumbers::PhoneNumber number;
+	i18n::phonenumbers::PhoneNumber phone_number;
 #endif /* ENABLE_PHONENUMBER */
 };
 
@@ -175,7 +175,7 @@ e_phone_number_from_string (const gchar *phone_number,
 
 	const PhoneNumberUtil::ErrorType err =
 		e_phone_number_util_get_instance ()->Parse (phone_number, country_code,
-		                                            &parsed_number->number);
+		                                            &parsed_number->phone_number);
 
 	if (err != PhoneNumberUtil::NO_PARSING_ERROR) {
 		e_phone_number_set_error (error, e_phone_number_error_code (err));
@@ -204,7 +204,7 @@ e_phone_number_to_string (const EPhoneNumber *phone_number,
 	std::string formatted_number;
 
 	e_phone_number_util_get_instance ()->Format
-		(phone_number->number,
+		(phone_number->phone_number,
 		 static_cast<PhoneNumberUtil::PhoneNumberFormat> (format),
 		 &formatted_number);
 
@@ -220,6 +220,83 @@ e_phone_number_to_string (const EPhoneNumber *phone_number,
 	return NULL;
 }
 
+static EPhoneNumberMatch
+e_phone_number_match (PhoneNumberUtil::MatchType match_type)
+{
+	switch (match_type) {
+	case PhoneNumberUtil::NO_MATCH:
+	case PhoneNumberUtil::INVALID_NUMBER:
+		return E_PHONE_NUMBER_MATCH_NONE;
+	case PhoneNumberUtil::SHORT_NSN_MATCH:
+		return E_PHONE_NUMBER_MATCH_SHORT;
+	case PhoneNumberUtil::NSN_MATCH:
+		return E_PHONE_NUMBER_MATCH_NATIONAL;
+	case PhoneNumberUtil::EXACT_MATCH:
+		return E_PHONE_NUMBER_MATCH_EXACT;
+	}
+
+	g_return_val_if_reached (E_PHONE_NUMBER_MATCH_NONE);
+}
+
+EPhoneNumberMatch
+e_phone_number_compare	(const EPhoneNumber *first_number,
+			 const EPhoneNumber *second_number)
+{
+	g_return_val_if_fail (NULL != first_number, E_PHONE_NUMBER_MATCH_NONE);
+	g_return_val_if_fail (NULL != second_number, E_PHONE_NUMBER_MATCH_NONE);
+
+	EPhoneNumberMatch result = E_PHONE_NUMBER_MATCH_NONE;
+
+#ifdef ENABLE_PHONENUMBER
+
+	const PhoneNumberUtil::MatchType match_type =
+		e_phone_number_util_get_instance ()->
+		IsNumberMatch (first_number->phone_number,
+		               second_number->phone_number);
+
+	g_warn_if_fail (match_type != PhoneNumberUtil::INVALID_NUMBER);
+	result = e_phone_number_match (match_type);
+
+#else /* ENABLE_PHONENUMBER */
+
+	e_phone_number_set_error (error, E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED);
+
+#endif /* ENABLE_PHONENUMBER */
+
+	return result;
+}
+
+EPhoneNumberMatch
+e_phone_number_compare_strings	(const gchar *first_number,
+				 const gchar *second_number,
+				 GError **error)
+{
+	g_return_val_if_fail (NULL != first_number, E_PHONE_NUMBER_MATCH_NONE);
+	g_return_val_if_fail (NULL != second_number, E_PHONE_NUMBER_MATCH_NONE);
+
+	EPhoneNumberMatch result = E_PHONE_NUMBER_MATCH_NONE;
+
+#ifdef ENABLE_PHONENUMBER
+
+	const PhoneNumberUtil::MatchType match_type =
+		e_phone_number_util_get_instance ()->
+		IsNumberMatchWithTwoStrings (first_number, second_number);
+
+	if (match_type == PhoneNumberUtil::INVALID_NUMBER) {
+		e_phone_number_set_error (error, E_PHONE_NUMBER_ERROR_NOT_A_NUMBER);
+	} else {
+		result = e_phone_number_match (match_type);
+	}
+
+#else /* ENABLE_PHONENUMBER */
+
+	e_phone_number_set_error (error, E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED);
+
+#endif /* ENABLE_PHONENUMBER */
+
+	return result;
+}
+
 EPhoneNumber *
 e_phone_number_copy (const EPhoneNumber *phone_number)
 {
diff --git a/libedataserver/e-phone-utils.h b/libedataserver/e-phone-utils.h
index 844c60d..1ee9f39 100644
--- a/libedataserver/e-phone-utils.h
+++ b/libedataserver/e-phone-utils.h
@@ -65,6 +65,22 @@ typedef enum {
 } EPhoneNumberFormat;
 
 /**
+ * EPhoneNumberMatch:
+ * @E_PHONE_NUMBER_MATCH_NONE: The phone numbers did not match.
+ * @E_PHONE_NUMBER_MATCH_EXACT: The phone numbers matched exactly.
+ * @E_PHONE_NUMBER_MATCH_NATIONAL: There was no country code for at least
+ * one of the numbers, but the national parts matched.
+ * @E_PHONE_NUMBER_MATCH_SHORT: There was no country code for at least
+ * one of the numbers, but one number might be part (suffix) of the other.
+ */
+typedef enum {
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_EXACT,
+	E_PHONE_NUMBER_MATCH_NATIONAL = 1024,
+	E_PHONE_NUMBER_MATCH_SHORT = 2048
+} EPhoneNumberMatch;
+
+/**
  * EPhoneNumberError:
  * @E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED: the library was built without phone
  * number support
@@ -151,6 +167,36 @@ gchar *			e_phone_number_to_string	(const EPhoneNumber *phone_number,
 							 EPhoneNumberFormat format);
 
 /**
+ * e_phone_number_compare:
+ * @first_number: the first EPhoneNumber to compare
+ * @second_number: the second EPhoneNumber to compare
+ *
+ * Compares two phone numbers.
+ *
+ * Returns: The quality of matching for the two phone numbers.
+ *
+ * Since: 3.8
+ */
+EPhoneNumberMatch	e_phone_number_compare		(const EPhoneNumber *first_number,
+							 const EPhoneNumber *second_number);
+
+/**
+ * e_phone_number_compare_strings:
+ * @first_number: the first EPhoneNumber to compare
+ * @second_number: the second EPhoneNumber to compare
+ * @error: (out): a #GError to set an error, if any
+ *
+ * Compares two phone numbers.
+ *
+ * Returns: The quality of matching for the two phone numbers.
+ *
+ * Since: 3.8
+ */
+EPhoneNumberMatch	e_phone_number_compare_strings	(const gchar *first_number,
+							 const gchar *second_number,
+							 GError **error);
+
+/**
  * e_phone_number_copy:
  * @phone_number: the EPhoneNumber to copy
  *
diff --git a/tests/libebook/data/vcards/custom-1.vcf b/tests/libebook/data/vcards/custom-1.vcf
index bc9a23c..6d7241b 100644
--- a/tests/libebook/data/vcards/custom-1.vcf
+++ b/tests/libebook/data/vcards/custom-1.vcf
@@ -1,5 +1,5 @@
 BEGIN:VCARD
 FN:Micheal Jackson
-TEL;HOME:+1234567
+TEL;HOME:+1-221-5423789
 EMAIL;TYPE=home,work:micheal jackson com
 END:VCARD
diff --git a/tests/libedataserver/e-phone-utils-test.c b/tests/libedataserver/e-phone-utils-test.c
index c085cba..32d9d17 100644
--- a/tests/libedataserver/e-phone-utils-test.c
+++ b/tests/libedataserver/e-phone-utils-test.c
@@ -25,6 +25,73 @@
 
 #include <libedataserver/libedataserver.h>
 
+static const char *match_candidates[] = {
+	"not-a-number",
+	"+1-617-4663489", "617-4663489", "4663489",
+	"+1.408.845.5246", "4088455246", "8455246"
+};
+
+static const EPhoneNumberMatch expected_matches[] = {
+	/* not a number */
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+
+	/* +1-617-4663489 */
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_EXACT,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_NATIONAL, /* XXX - Google, sure? */
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+
+	/* +1.408.845.5246 */
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_EXACT,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_SHORT,
+
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_NATIONAL,
+	E_PHONE_NUMBER_MATCH_SHORT,
+
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_NONE,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_SHORT,
+	E_PHONE_NUMBER_MATCH_NATIONAL /* XXX - Google, sure? */
+};
+
 static void
 test_parse_and_format (gconstpointer data)
 {
@@ -95,33 +162,89 @@ test_parse_bad_number (void)
 	g_clear_error (&error);
 }
 
+static void
+test_compare_numbers (gconstpointer data)
+{
+	const size_t n = GPOINTER_TO_UINT (data);
+	const size_t i = n % G_N_ELEMENTS (match_candidates);
+	const size_t j = n / G_N_ELEMENTS (match_candidates);
+
+#ifdef ENABLE_PHONENUMBER
+	const gboolean error_expected = !(i && j) ;
+#else /* ENABLE_PHONENUMBER */
+	const gboolean error_expected = TRUE;
+#endif /* ENABLE_PHONENUMBER */
+
+	EPhoneNumberMatch actual_match;
+	GError *error = NULL;
+
+	actual_match = e_phone_number_compare_strings (match_candidates[i],
+	                                               match_candidates[j],
+	                                               &error);
+
+	g_assert_cmpuint (actual_match, ==, expected_matches[n]);
+
+	if (!error_expected) {
+		g_assert (error == NULL);
+	} else {
+		g_assert (error != NULL);
+		g_assert (error->domain == E_PHONE_NUMBER_ERROR);
+#ifdef ENABLE_PHONENUMBER
+		g_assert (error->code == E_PHONE_NUMBER_ERROR_NOT_A_NUMBER);
+#else /* ENABLE_PHONENUMBER */
+		g_assert (error->code == E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED);
+#endif /* ENABLE_PHONENUMBER */
+		g_assert (error->message != NULL);
+
+		g_clear_error (&error);
+	}
+}
+
 gint
 main (gint argc,
       gchar **argv)
 {
+	size_t i, j;
+
 	g_type_init ();
 
 	g_test_init (&argc, &argv, NULL);
 
 	g_test_add_data_func
-		("/e-phone-utils-test/ParseAndFormat/I164",
+		("/e-phone-utils-test/parse-and-format/i164",
 		 "+493011223344//+493011223344/+49 30 11223344/030 11223344/tel:+49-30-11223344",
 		 test_parse_and_format);
 	g_test_add_data_func
-		("/e-phone-utils-test/ParseAndFormat/National",
+		("/e-phone-utils-test/parse-and-format/national",
 		 "(030) 22334-455/DE/+493022334455/+49 30 22334455/030 22334455/tel:+49-30-22334455",
 		 test_parse_and_format);
 	g_test_add_data_func
-		("/e-phone-utils-test/ParseAndFormat/International",
+		("/e-phone-utils-test/parse-and-format/international",
 		 "+1 212 33445566//+121233445566/+1 21233445566/21233445566/tel:+1-21233445566",
 		 test_parse_and_format);
 	g_test_add_data_func
-		("/e-phone-utils-test/ParseAndFormat/RFC3966",
+		("/e-phone-utils-test/parse-and-format/rfc3966",
 		 "tel:+358-71-44556677//+3587144556677/+358 71 44556677/071 44556677/tel:+358-71-44556677",
 		 test_parse_and_format);
+
 	g_test_add_func
-		("/e-phone-utils-test/ParseAndFormat/BadNumber",
+		("/e-phone-utils-test/parse-and-format/BadNumber",
 		 test_parse_bad_number);
 
+	g_assert_cmpint (G_N_ELEMENTS (match_candidates) * G_N_ELEMENTS (match_candidates),
+			 ==, G_N_ELEMENTS (expected_matches));
+
+	for (i = 0; i < G_N_ELEMENTS (match_candidates); ++i) {
+		for (j = 0; j < G_N_ELEMENTS (match_candidates); ++j) {
+			const size_t n = j * G_N_ELEMENTS (match_candidates) + i;
+			char *path = g_strdup_printf ("/e-phone-utils-test/compare/%s/%s",
+			                              match_candidates[i],
+			                              match_candidates[j]);
+
+			g_test_add_data_func (path, GUINT_TO_POINTER (n), test_compare_numbers);
+			g_free (path);
+		}
+	}
+
 	return g_test_run ();
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]