From: Mathias Hasselmann Date: Tue, 4 Dec 2012 23:47:07 +0000 (+0100) Subject: libebook: Add phone number utilities X-Git-Tag: upstream/3.7.5~143 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=89e444369e5f2916c5ebbdaad34af1d2b92168f4;p=platform%2Fupstream%2Fevolution-data-server.git libebook: Add phone number utilities This functionality covers parsing, formatting and comparing phone numbers. Note that this commit introduces an optional depedency on C++. See: https://bugzilla.gnome.org/show_bug.cgi?id=689622 --- diff --git a/addressbook/libebook/Makefile.am b/addressbook/libebook/Makefile.am index 5e29354..b015dd3 100644 --- a/addressbook/libebook/Makefile.am +++ b/addressbook/libebook/Makefile.am @@ -48,8 +48,10 @@ libebook_1_2_la_SOURCES = \ e-contact.c \ e-destination.c \ e-name-western.c \ - e-name-western-tables.h \ - e-source-backend-summary-setup.c \ + e-name-western-tables.h \ + e-phone-number.c \ + e-phone-number-private.h \ + e-source-backend-summary-setup.c \ e-vcard.c \ e-error.h @@ -65,6 +67,12 @@ libebook_1_2_la_LDFLAGS = \ $(CODE_COVERAGE_LDFLAGS) \ $(NULL) +if ENABLE_PHONENUMBER +libebook_1_2_la_SOURCES += e-phone-number-private.cpp +libebook_1_2_la_CPPFLAGS += $(PHONENUMBER_INCLUDES) +libebook_1_2_la_LIBADD += $(PHONENUMBER_LIBS) +endif # ENABLE_PHONENUMBER + libebookincludedir = $(privincludedir)/libebook libebookinclude_HEADERS = \ @@ -80,6 +88,7 @@ libebookinclude_HEADERS = \ e-contact.h \ e-destination.h \ e-name-western.h \ + e-phone-number.h \ e-source-backend-summary-setup.h \ e-vcard.h diff --git a/addressbook/libebook/e-phone-number-private.cpp b/addressbook/libebook/e-phone-number-private.cpp new file mode 100644 index 0000000..99caa7f --- /dev/null +++ b/addressbook/libebook/e-phone-number-private.cpp @@ -0,0 +1,212 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifndef ENABLE_PHONENUMBER +#error Phone number support must be enabled for this file +#endif /* ENABLE_PHONENUMBER */ + +#include "e-phone-number-private.h" + +/* C++ standard library */ +#include + +/* system headers */ +#include + +/* libphonenumber */ +#include +#include + +using i18n::phonenumbers::PhoneNumberUtil; + +struct _EPhoneNumber { + i18n::phonenumbers::PhoneNumber phone_number; +}; + +G_DEFINE_BOXED_TYPE (EPhoneNumber, + e_phone_number, + e_phone_number_copy, + e_phone_number_free) + +static PhoneNumberUtil * +e_phone_number_util_get_instance (void) +{ + static PhoneNumberUtil *instance = NULL; + + if (g_once_init_enter (&instance)) { + /* FIXME: Ideally PhoneNumberUtil would not be a singleton, + * so that we could safely tweak it's attributes without + * influencing other users of the library. */ + PhoneNumberUtil *new_instance = PhoneNumberUtil::GetInstance (); + + /* Disable all logging: libphonenumber is pretty verbose. */ + new_instance->SetLogger (new i18n::phonenumbers::NullLogger); + g_once_init_leave (&instance, new_instance); + } + + return instance; +} + +static EPhoneNumberError +e_phone_number_error_code (PhoneNumberUtil::ErrorType error) +{ + switch (error) { + case PhoneNumberUtil::NO_PARSING_ERROR: + g_return_val_if_reached (E_PHONE_NUMBER_ERROR_UNKNOWN); + case PhoneNumberUtil::INVALID_COUNTRY_CODE_ERROR: + return E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE; + case PhoneNumberUtil::NOT_A_NUMBER: + return E_PHONE_NUMBER_ERROR_NOT_A_NUMBER; + case PhoneNumberUtil::TOO_SHORT_AFTER_IDD: + return E_PHONE_NUMBER_ERROR_TOO_SHORT_AFTER_IDD; + case PhoneNumberUtil::TOO_SHORT_NSN: + return E_PHONE_NUMBER_ERROR_TOO_SHORT; + case PhoneNumberUtil::TOO_LONG_NSN: + return E_PHONE_NUMBER_ERROR_TOO_LONG; + } + + /* Please file a bug that we can add a proper error code. */ + g_return_val_if_reached (E_PHONE_NUMBER_ERROR_UNKNOWN); +} + +EPhoneNumber * +_e_phone_number_cxx_from_string (const gchar *phone_number, + const gchar *country_code, + GError **error) +{ + g_return_val_if_fail (NULL != phone_number, NULL); + + if (country_code == NULL) { +#if HAVE__NL_ADDRESS_COUNTRY_AB2 + country_code = nl_langinfo (_NL_ADDRESS_COUNTRY_AB2); +#else /* HAVE__NL_ADDRESS_COUNTRY_AB2 */ +#error Cannot resolve default 2-letter country code. Find a replacement for _NL_ADDRESS_COUNTRY_AB2 or implement code to parse the locale name. +#endif /* HAVE__NL_ADDRESS_COUNTRY_AB2 */ + } + + std::auto_ptr parsed_number(new EPhoneNumber); + + const PhoneNumberUtil::ErrorType err = + e_phone_number_util_get_instance ()->Parse (phone_number, country_code, + &parsed_number->phone_number); + + if (err != PhoneNumberUtil::NO_PARSING_ERROR) { + _e_phone_number_set_error (error, e_phone_number_error_code (err)); + return NULL; + } + + return parsed_number.release(); +} + +gchar * +_e_phone_number_cxx_to_string (const EPhoneNumber *phone_number, + EPhoneNumberFormat format) +{ + g_return_val_if_fail (NULL != phone_number, NULL); + + std::string formatted_number; + + e_phone_number_util_get_instance ()->Format + (phone_number->phone_number, + static_cast (format), + &formatted_number); + + if (!formatted_number.empty ()) + return g_strdup (formatted_number.c_str ()); + + 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_cxx_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); + + 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); + return e_phone_number_match (match_type); +} + +EPhoneNumberMatch +_e_phone_number_cxx_compare_strings (const gchar *first_number, + const gchar *second_number, + GError **error) +{ + EPhoneNumberMatch result = E_PHONE_NUMBER_MATCH_NONE; + + 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); + + 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); + } + + return result; +} + +EPhoneNumber * +_e_phone_number_cxx_copy (const EPhoneNumber *phone_number) +{ + if (phone_number) + return new EPhoneNumber (*phone_number); + + return NULL; +} + +void +_e_phone_number_cxx_free (EPhoneNumber *phone_number) +{ + delete phone_number; +} diff --git a/addressbook/libebook/e-phone-number-private.h b/addressbook/libebook/e-phone-number-private.h new file mode 100644 index 0000000..091f18e --- /dev/null +++ b/addressbook/libebook/e-phone-number-private.h @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +/* NOTE: Keeping API documentation in this header file because gtkdoc-mkdb + * explicitly only scans .h and .c files, but ignores .cpp files. */ + +/** + * SECTION: e-phone-utils + * @include: libedataserver/libedataserver.h + * @short_description: Phone number support + * + * This modules provides utility functions for parsing and formatting + * phone numbers. Under the hood it uses Google's libphonenumber. + **/ + +#if !defined (__LIBEBOOK_H_INSIDE__) && !defined (LIBEBOOK_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_PHONE_NUMBER_PRIVATE_H +#define E_PHONE_NUMBER_PRIVATE_H + +#include "e-phone-number.h" + +G_BEGIN_DECLS + +#if __GNUC__ >= 4 +#define E_PHONE_NUMBER_LOCAL __attribute__ ((visibility ("hidden"))) +#else +#define E_PHONE_NUMBER_LOCAL +#endif + +/* defined and used in e-phone-number.c, but also used by e-phone-number-private.cpp */ + +E_PHONE_NUMBER_LOCAL void _e_phone_number_set_error (GError **error, + EPhoneNumberError code); + +#ifdef ENABLE_PHONENUMBER + +/* defined in e-phone-number-private.cpp, and used by by e-phone-number.c */ + +E_PHONE_NUMBER_LOCAL EPhoneNumber * _e_phone_number_cxx_from_string (const gchar *phone_number, + const gchar *country_code, + GError **error); +E_PHONE_NUMBER_LOCAL gchar * _e_phone_number_cxx_to_string (const EPhoneNumber *phone_number, + EPhoneNumberFormat format); +E_PHONE_NUMBER_LOCAL EPhoneNumberMatch _e_phone_number_cxx_compare (const EPhoneNumber *first_number, + const EPhoneNumber *second_number); +E_PHONE_NUMBER_LOCAL EPhoneNumberMatch _e_phone_number_cxx_compare_strings (const gchar *first_number, + const gchar *second_number, + GError **error); +E_PHONE_NUMBER_LOCAL EPhoneNumber * _e_phone_number_cxx_copy (const EPhoneNumber *phone_number); +E_PHONE_NUMBER_LOCAL void _e_phone_number_cxx_free (EPhoneNumber *phone_number); + +#endif /* ENABLE_PHONENUMBER */ + +G_END_DECLS + +#endif /* E_PHONE_NUMBER_PRIVATE_H */ diff --git a/addressbook/libebook/e-phone-number.c b/addressbook/libebook/e-phone-number.c new file mode 100644 index 0000000..a24272d --- /dev/null +++ b/addressbook/libebook/e-phone-number.c @@ -0,0 +1,283 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include "e-phone-number.h" + +#include + +#include "e-phone-number-private.h" + +#ifndef ENABLE_PHONENUMBER + +/* With phonenumber support enabled the boxed type must be defined in + * the C++ code because we cannot compute the size of C++ types here. */ +G_DEFINE_BOXED_TYPE (EPhoneNumber, + e_phone_number, + e_phone_number_copy, + e_phone_number_free) + +#endif /* ENABLE_PHONENUMBER */ + +GQuark +e_phone_number_error_quark (void) +{ + static GQuark q = 0; + + if (q == 0) + q = g_quark_from_static_string ("e-phone-number-error-quark"); + + return q; +} + +static const gchar * +e_phone_number_error_to_string (EPhoneNumberError code) +{ + switch (code) { + case E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED: + return _("The library was built without phone number support."); + case E_PHONE_NUMBER_ERROR_UNKNOWN: + return _("The phone number parser reported an yet unkown error code."); + case E_PHONE_NUMBER_ERROR_NOT_A_NUMBER: + return _("Not a phone number"); + case E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE: + return _("Invalid country code"); + case E_PHONE_NUMBER_ERROR_TOO_SHORT_AFTER_IDD: + return _("Remaining text after the country code is to short for a phone number"); + case E_PHONE_NUMBER_ERROR_TOO_SHORT: + return _("Text is too short for a phone number"); + case E_PHONE_NUMBER_ERROR_TOO_LONG: + return _("Text is too long for a phone number"); + } + + return _("Unknown error"); +} + +void +_e_phone_number_set_error (GError **error, + EPhoneNumberError code) +{ + const gchar *message = e_phone_number_error_to_string (code); + g_set_error_literal (error, E_PHONE_NUMBER_ERROR, code, message); +} + +/** + * e_phone_number_is_supported: + * + * Checks if phone number support is available. It is recommended to call this + * function before using any of the phone-utils functions to ensure that the + * required functionality is available, and to pick alternative mechnisms if + * needed. + * + * Returns: %TRUE if phone number support is available. + **/ +gboolean +e_phone_number_is_supported (void) +{ +#ifdef ENABLE_PHONENUMBER + + return TRUE; + +#else /* ENABLE_PHONENUMBER */ + + return FALSE; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * e_phone_number_from_string: + * @phone_number: the phone number to parse + * @country_code: (allow-none): a 2-letter country code, or %NULL + * @error: (out): a #GError to set an error, if any + * + * Parses the string passed in @phone_number. Note that no validation is + * performed whether the recognized phone number is valid for a particular + * region. + * + * The 2-letter country code passed in @country_code only is used if the + * @phone_number is not written in international format. The applications's + * currently locale is consulted if %NULL gets passed for @country_code. + * If the number is guaranteed to start with a '+' followed by the country + * calling code, then "ZZ" can be passed here. + * + * Returns: (transfer full): a new EPhoneNumber instance on success, + * or %NULL on error. Call e_phone_number_free() to release this instance. + * + * Since: 3.8 + **/ +EPhoneNumber * +e_phone_number_from_string (const gchar *phone_number, + const gchar *country_code, + GError **error) +{ +#ifdef ENABLE_PHONENUMBER + + return _e_phone_number_cxx_from_string (phone_number, country_code, error); + +#else /* ENABLE_PHONENUMBER */ + + _e_phone_number_set_error (error, E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED); + return NULL; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * e_phone_number_to_string: + * @phone_number: the phone number to format + * @format: the phone number format to apply + * + * Describes the @phone_number according to the rules applying to @format. + * + * Returns: (transfer full): A formatted string for @phone_number. + * + * Since: 3.8 + **/ +gchar * +e_phone_number_to_string (const EPhoneNumber *phone_number, + EPhoneNumberFormat format) +{ +#ifdef ENABLE_PHONENUMBER + + return _e_phone_number_cxx_to_string (phone_number, format); + +#else /* ENABLE_PHONENUMBER */ + + g_warning ("%s: The library was built without phone number support.", G_STRFUNC); + return NULL; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * 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) +{ +#ifdef ENABLE_PHONENUMBER + + return _e_phone_number_cxx_compare (first_number, second_number); + +#else /* ENABLE_PHONENUMBER */ + + /* NOTE: This calls for a dedicated return value, but I sense broken + * client code that only checks for E_PHONE_NUMBER_MATCH_NONE and then + * treats the "not-implemented" return value as a match */ + g_warning ("%s: The library was built without phone number support.", G_STRFUNC); + return E_PHONE_NUMBER_MATCH_NONE; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * 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) +{ +#ifdef ENABLE_PHONENUMBER + + return _e_phone_number_cxx_compare_strings (first_number, second_number, error); + +#else /* ENABLE_PHONENUMBER */ + + _e_phone_number_set_error (error, E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED); + return E_PHONE_NUMBER_MATCH_NONE; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * e_phone_number_copy: + * @phone_number: the EPhoneNumber to copy + * + * Makes a copy of @phone_number. + * + * Returns: (transfer full): A newly allocated EPhoneNumber instance. + * Call e_phone_number_free() to release this instance. + * + * Since: 3.8 + **/ +EPhoneNumber * +e_phone_number_copy (const EPhoneNumber *phone_number) +{ +#ifdef ENABLE_PHONENUMBER + + return _e_phone_number_cxx_copy (phone_number); + +#else /* ENABLE_PHONENUMBER */ + + /* Without phonenumber support there are no instances. + * Any non-NULL value is a programming error in this setup. */ + g_warn_if_fail (phone_number == NULL); + return NULL; + +#endif /* ENABLE_PHONENUMBER */ +} + +/** + * e_phone_number_free: + * @phone_number: the EPhoneNumber to free + * + * Released the memory occupied by @phone_number. + * + * Since: 3.8 + **/ +void +e_phone_number_free (EPhoneNumber *phone_number) +{ +#ifdef ENABLE_PHONENUMBER + + _e_phone_number_cxx_free (phone_number); + +#else /* ENABLE_PHONENUMBER */ + + /* Without phonenumber support there are no instances. + * Any non-NULL value is a programming error in this setup. */ + g_warn_if_fail (phone_number == NULL); + +#endif /* ENABLE_PHONENUMBER */ +} diff --git a/addressbook/libebook/e-phone-number.h b/addressbook/libebook/e-phone-number.h new file mode 100644 index 0000000..2e33835 --- /dev/null +++ b/addressbook/libebook/e-phone-number.h @@ -0,0 +1,188 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +/** + * SECTION: e-phone-number + * @include: libedataserver/libedataserver.h + * @short_description: Phone number support + * + * This modules provides utility functions for parsing and formatting + * phone numbers. Under the hood it uses Google's libphonenumber. + **/ + +#if !defined (__LIBEBOOK_H_INSIDE__) && !defined (LIBEBOOK_COMPILATION) +#error "Only should be included directly." +#endif + +#ifndef E_PHONE_NUMBER_H +#define E_PHONE_NUMBER_H + +#include + +G_BEGIN_DECLS + +#define E_TYPE_PHONE_NUMBER (e_phone_number_get_type ()) +#define E_PHONE_NUMBER_ERROR (e_phone_number_error_quark ()) + +/** + * EPhoneNumberFormat: + * @E_PHONE_NUMBER_FORMAT_E164: format according E.164: "+493055667788". + * @E_PHONE_NUMBER_FORMAT_INTERNATIONAL: a formatted phone number always + * starting with the country calling code: "+49 30 55667788". + * @E_PHONE_NUMBER_FORMAT_NATIONAL: a formatted phone number in national + * scope, that is without country code: "(030) 55667788". + * @E_PHONE_NUMBER_FORMAT_RFC3966: a tel: URL according to RFC 3966: + * "tel:+49-30-55667788". + * + * The supported formatting rules for phone numbers. + **/ +typedef enum { + E_PHONE_NUMBER_FORMAT_E164, + E_PHONE_NUMBER_FORMAT_INTERNATIONAL, + E_PHONE_NUMBER_FORMAT_NATIONAL, + E_PHONE_NUMBER_FORMAT_RFC3966 +} 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. + * + * The quality of a phone number match. + + * Let's consider the phone number "+1-221-5423789", then comparing with + * "+1.221.542.3789" we have get E_PHONE_NUMBER_MATCH_EXACT because country + * code, region code and local number are matching. Comparing with "2215423789" + * will result in E_PHONE_NUMBER_MATCH_NATIONAL because the country code is + * missing, but the national portion is matching. Finally comparing with + * "5423789" gives E_PHONE_NUMBER_MATCH_SHORT. For more detail have a look at + * the following table: + * + * + * + * + * + * + * + * + * + * + * + * + * +1-617-5423789 + * +1-221-5423789 + * 221-5423789 + * 5423789 + * + * +1-617-5423789 + * exact + * none + * none + * short + * + * +1-221-5423789 + * none + * exact + * national + * short + * + * 221-5423789 + * none + * national + * national + * short + * + * 5423789 + * short + * short + * short + * short + * + * + * + */ +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 + * @E_PHONE_NUMBER_ERROR_UNKNOWN: the phone number parser reported an yet + * unkown error code. + * @E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE: the supplied phone number has an + * invalid country code. + * @E_PHONE_NUMBER_ERROR_NOT_A_NUMBER: the supplied text is not a phone number. + * @E_PHONE_NUMBER_ERROR_TOO_SHORT_AFTER_IDD: the remaining text after the + * country code is to short for a phone number. + * @E_PHONE_NUMBER_ERROR_TOO_SHORT: the text is too short for a phone number. + * @E_PHONE_NUMBER_ERROR_TOO_LONG: the text is too long for a phone number. + * + * Numeric description of a phone number related error. + **/ +typedef enum { + E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED, + E_PHONE_NUMBER_ERROR_UNKNOWN, + E_PHONE_NUMBER_ERROR_NOT_A_NUMBER, + E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE, + E_PHONE_NUMBER_ERROR_TOO_SHORT_AFTER_IDD, + E_PHONE_NUMBER_ERROR_TOO_SHORT, + E_PHONE_NUMBER_ERROR_TOO_LONG +} EPhoneNumberError; + +/** + * EPhoneNumber: + * This opaque type describes a parsed phone number. It can be copied using + * e_phone_number_copy(). To release it call e_phone_number_free(). + */ +typedef struct _EPhoneNumber EPhoneNumber; + +GType e_phone_number_get_type (void); +GQuark e_phone_number_error_quark (void); + +gboolean e_phone_number_is_supported (void) G_GNUC_CONST; + +EPhoneNumber * e_phone_number_from_string (const gchar *phone_number, + const gchar *country_code, + GError **error); +gchar * e_phone_number_to_string (const EPhoneNumber *phone_number, + EPhoneNumberFormat format); + +EPhoneNumberMatch e_phone_number_compare (const EPhoneNumber *first_number, + const EPhoneNumber *second_number); +EPhoneNumberMatch e_phone_number_compare_strings (const gchar *first_number, + const gchar *second_number, + GError **error); + +EPhoneNumber * e_phone_number_copy (const EPhoneNumber *phone_number); +void e_phone_number_free (EPhoneNumber *phone_number); + +G_END_DECLS + +#endif /* E_PHONE_NUMBER_H */ diff --git a/addressbook/libebook/libebook.h b/addressbook/libebook/libebook.h index b1ddffc..f8eafb0 100644 --- a/addressbook/libebook/libebook.h +++ b/addressbook/libebook/libebook.h @@ -34,6 +34,7 @@ #include #include #include +#include #include #include diff --git a/configure.ac b/configure.ac index 1128e30..68b495b 100644 --- a/configure.ac +++ b/configure.ac @@ -272,6 +272,18 @@ localedir='$(prefix)/$(DATADIRNAME)/locale' AC_SUBST(localedir) dnl ****************************** +dnl libphonenumber support +dnl ****************************** + +dnl The EVO_PHONENUMBER_SUPPORT macro calls AC_PROG_CXX if libphonenumber +dnl support got requested. Therefore this macro must be expanded before +dnl the libtool macros. Feel free to move back to the other optional +dnl dependencies if you know how to fix the autoconf issue, or if you +dnl concluded that C++ actually is pretty awesome and should be a hard +dnl dependency. +EVO_PHONENUMBER_SUPPORT + +dnl ****************************** dnl Initialize libtool dnl ****************************** LT_PREREQ(2.2) @@ -1672,6 +1684,7 @@ echo " Kerberos 5: $msg_krb5 SMIME support: $msg_smime IPv6 support: $msg_ipv6 + Phone number support: $msg_phonenumber Dot Locking: $msg_dot File Locking: $msg_file Large files: $enable_largefile diff --git a/docs/reference/addressbook/libebook/Makefile.am b/docs/reference/addressbook/libebook/Makefile.am index 1d96640..7c1fb1f 100644 --- a/docs/reference/addressbook/libebook/Makefile.am +++ b/docs/reference/addressbook/libebook/Makefile.am @@ -4,6 +4,11 @@ DOC_MODULE = libebook # The top-level SGML file. DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.sgml +# Extra options to supply to gtkdoc-mkdb +# We must enable the SGML mode so that we can have HTML tables +# in the documentation (e.g. to illustrate phone number matching). +MKDB_OPTIONS = --sgml-mode --output-format=xml --name-space=e + # Extra options to supply to gtkdoc-scan SCAN_OPTIONS = --deprecated-guards="EDS_DISABLE_DEPRECATED" diff --git a/docs/reference/addressbook/libebook/libebook-docs.sgml b/docs/reference/addressbook/libebook/libebook-docs.sgml index 0c2fc79..7144490 100644 --- a/docs/reference/addressbook/libebook/libebook-docs.sgml +++ b/docs/reference/addressbook/libebook/libebook-docs.sgml @@ -20,6 +20,7 @@ + diff --git a/docs/reference/addressbook/libebook/libebook-sections.txt b/docs/reference/addressbook/libebook/libebook-sections.txt index 2bb7125..75d842e 100644 --- a/docs/reference/addressbook/libebook/libebook-sections.txt +++ b/docs/reference/addressbook/libebook/libebook-sections.txt @@ -553,3 +553,23 @@ e_source_backend_summary_setup_get_type ESourceBackendSummarySetupPrivate + +
+e-phone-number +EPhoneNumber +EPhoneNumberError +EPhoneNumberFormat +EPhoneNumberMatch +e_phone_number_is_supported +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 + +E_PHONE_NUMBER_ERROR +E_TYPE_PHONE_NUMBER +e_phone_number_error_quark +e_phone_number_get_type +
diff --git a/m4/evo_phonenumber.m4 b/m4/evo_phonenumber.m4 new file mode 100644 index 0000000..ba0e00b --- /dev/null +++ b/m4/evo_phonenumber.m4 @@ -0,0 +1,69 @@ +dnl EVO_PHONENUMBER_SUPPORT([default]) +dnl Check for Google's libphonenumber. Adds a --with-phonenumber option +dnl to explicitly enable and disable phonenumber support, but also for +dnl pointing to libphonenumber's install prefix. +AC_DEFUN([EVO_PHONENUMBER_SUPPORT],[ + AC_MSG_CHECKING([whether to enable phonenumber support]) + + evo_phonenumber_prefix= + msg_phonenumber=no + + PHONENUMBER_INCLUDES= + PHONENUMBER_LIBS= + + AC_ARG_WITH([phonenumber], + [AS_HELP_STRING([--with-phonenumber@<:@=PREFIX@:>@], + [use libphonenumber at PREFIX])], + [evo_phonenumber_prefix=$withval], + [with_phonenumber=m4_default([$1],[check])]) + + AC_MSG_RESULT([$with_phonenumber]) + + AS_VAR_IF([with_phonenumber], [no],, [ + AC_REQUIRE([AC_PROG_CXX]) + AC_LANG_PUSH(C++) + + PHONENUMBER_LIBS="-lphonenumber -lboost_thread" + + AS_VAR_IF([evo_phonenumber_prefix],,,[ + PHONENUMBER_INCLUDES="-I$evo_phonenumber_prefix/include" + PHONENUMBER_LIBS="-L$evo_phonenumber_prefix/lib $PHONENUMBER_LIBS" + ]) + + evo_cxxflags_saved="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $PHONENUMBER_INCLUDES" + + evo_libs_saved="$LIBS" + LIBS="$LIBS $PHONENUMBER_LIBS" + + AC_MSG_CHECKING([if libphonenumber is usable]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include ]], + [[i18n::phonenumbers::PhoneNumberUtil::GetInstance();]])], + [with_phonenumber=yes], + [AS_VAR_IF([with_phonenumber], [check], [with_phonenumber=no], [ + AC_MSG_ERROR([libphonenumber cannot be used. Use --with-phonenumber to specify the library prefix.])]) + ]) + + CXXFLAGS="$evo_cxxflags_saved" + LDFLAGS="$evo_ldflags_saved" + LIBS="$evo_libs_saved" + + AS_VAR_IF([evo_phonenumber_prefix],, + [msg_phonenumber=$with_phonenumber], + [msg_phonenumber=$evo_phonenumber_prefix]) + + AC_MSG_RESULT([$with_phonenumber]) + AC_LANG_POP(C++) + ]) + + AM_CONDITIONAL([ENABLE_PHONENUMBER], + [test "x$with_phonenumber" != "xno"]) + + AS_VAR_IF([with_phonenumber], [yes], + [AC_DEFINE([ENABLE_PHONENUMBER], 1, [Enable phonenumber parsing])]) + + AC_SUBST([PHONENUMBER_INCLUDES]) + AC_SUBST([PHONENUMBER_LIBS]) +]) diff --git a/tests/libebook/Makefile.am b/tests/libebook/Makefile.am index 7fff918..7b365bf 100644 --- a/tests/libebook/Makefile.am +++ b/tests/libebook/Makefile.am @@ -51,6 +51,7 @@ DEPRECATED_TESTS = \ # Should be kept ordered approximately from least to most difficult/complex TESTS = \ + test-ebook-phone-number \ test-ebook-add-contact \ test-ebook-get-contact \ test-ebook-commit-contact \ @@ -134,6 +135,8 @@ test_ebook_remove_contact_by_id_LDADD=$(TEST_LIBS) test_ebook_remove_contact_by_id_CPPFLAGS=$(TEST_CPPFLAGS) test_ebook_remove_contacts_LDADD=$(TEST_LIBS) test_ebook_remove_contacts_CPPFLAGS=$(TEST_CPPFLAGS) +test_ebook_phone_number_LDADD=$(TEST_LIBS) +test_ebook_phone_number_CPPFLAGS=$(TEST_CPPFLAGS) #test_ebook_stress_factory__fifo_LDADD=$(TEST_LIBS) test_ebook_stress_factory__fifo_CPPFLAGS=$(TEST_CPPFLAGS) #test_ebook_stress_factory__serial_LDADD=$(TEST_LIBS) diff --git a/tests/libebook/test-ebook-phone-number.c b/tests/libebook/test-ebook-phone-number.c new file mode 100644 index 0000000..9d9e740 --- /dev/null +++ b/tests/libebook/test-ebook-phone-number.c @@ -0,0 +1,289 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2012,2013 Intel Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * 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 Lesser General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * Author: Mathias Hasselmann + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +static const char *match_candidates[] = { + "not-a-number", + "+1-617-4663489", "617-4663489", "4663489", + "+1.408.845.5246", "4088455246", "8455246", + "+1-857-4663489" +}; + +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, + 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, + + /* 617-4663489 */ + 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, + + /* 4663489 */ + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_SHORT, + E_PHONE_NUMBER_MATCH_SHORT, + E_PHONE_NUMBER_MATCH_NATIONAL, /* XXX - Google, really? I'd expect a full match here. */ + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_SHORT, + + /* +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, + + /* 4088455246 */ + 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, + + /* 8455246 */ + 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, really? I'd expect a full match here. */ + E_PHONE_NUMBER_MATCH_NONE, + + /* +1-857-4663489 */ + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_SHORT, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_NONE, + E_PHONE_NUMBER_MATCH_EXACT +}; + +static void +test_parse_and_format (gconstpointer data) +{ + GError *error = NULL; + EPhoneNumber *parsed; + gchar **params; + + params = g_strsplit (data, "/", G_MAXINT); + g_assert_cmpint (g_strv_length (params), ==, 6); + + parsed = e_phone_number_from_string (params[0], params[1], &error); + +#ifdef ENABLE_PHONENUMBER + + { + gchar **test_numbers; + gint i; + + test_numbers = params + 2; + + g_assert (parsed != NULL); + g_assert (error == NULL); + + for (i = 0; test_numbers[i]; ++i) { + gchar *formatted; + + formatted = e_phone_number_to_string (parsed, i); + g_assert (formatted != NULL); + g_assert_cmpstr (formatted, ==, test_numbers[i]); + g_free (formatted); + } + + e_phone_number_free (parsed); + } + +#else /* ENABLE_PHONENUMBER */ + + g_assert (parsed == NULL); + g_assert (error != NULL); + g_assert (error->domain == E_PHONE_NUMBER_ERROR); + g_assert (error->code == E_PHONE_NUMBER_ERROR_NOT_IMPLEMENTED); + g_assert (error->message != NULL); + +#endif /* ENABLE_PHONENUMBER */ + + g_clear_error (&error); + g_strfreev (params); +} + +static void +test_parse_bad_number (void) +{ + GError *error = NULL; + EPhoneNumber *parsed; + + parsed = e_phone_number_from_string ("+1-NOT-A-NUMBER", "US", &error); + + g_assert (parsed == NULL); + 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); +} + +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); + +#ifdef ENABLE_PHONENUMBER + g_assert_cmpuint (actual_match, ==, expected_matches[n]); +#else /* ENABLE_PHONENUMBER */ + g_assert_cmpuint (actual_match, ==, E_PHONE_NUMBER_MATCH_NONE); +#endif /* ENABLE_PHONENUMBER */ + + 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); + } +} + +static void +test_supported (void) +{ +#ifdef ENABLE_PHONENUMBER + g_assert (e_phone_number_is_supported ()); +#else /* ENABLE_PHONENUMBER */ + g_assert (!e_phone_number_is_supported ()); +#endif /* ENABLE_PHONENUMBER */ +} + +gint +main (gint argc, + gchar **argv) +{ + size_t i, j; + + g_type_init (); + + g_test_init (&argc, &argv, NULL); + + g_test_add_func + ("/ebook-phone-number/supported", + test_supported); + + g_test_add_data_func + ("/ebook-phone-number/parse-and-format/i164", + "+493011223344//+493011223344/+49 30 11223344/030 11223344/tel:+49-30-11223344", + test_parse_and_format); + g_test_add_data_func + ("/ebook-phone-number/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 + ("/ebook-phone-number/parse-and-format/international", + "+1 212 33445566//+121233445566/+1 21233445566/21233445566/tel:+1-21233445566", + test_parse_and_format); + g_test_add_data_func + ("/ebook-phone-number/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 + ("/ebook-phone-number/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 + i * G_N_ELEMENTS (match_candidates); + char *path = g_strdup_printf ("/ebook-phone-number/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 (); +}