[evolution-data-server/openismus-phonenumber-work: 3/14] libedataserver: Add basic phone number utilities



commit 8ac74dfdadc4edc4eae340b7307504aeb13ef831
Author: Mathias Hasselmann <mathias openismus com>
Date:   Wed Dec 5 00:47:07 2012 +0100

    libedataserver: Add basic phone number utilities
    
    For now this only covers parsing and formatting.
    Later there also will be matching.
    
    Note that this commit introduces a depedency on C++.
    
    Also note that the API documentation is kept in header files
    because gtkdoc-mkdb explicitly only scans .h and .c files,
    but ignores .cpp files.

 .../libedataserver/libedataserver-docs.sgml        |    1 +
 .../libedataserver/libedataserver-sections.txt     |   16 ++
 libedataserver/Makefile.am                         |    7 +
 libedataserver/e-phone-utils.cpp                   |  199 ++++++++++++++++++++
 libedataserver/e-phone-utils.h                     |  160 ++++++++++++++++
 libedataserver/libedataserver.h                    |    1 +
 tests/libedataserver/Makefile.am                   |   13 +-
 tests/libedataserver/e-phone-utils-test.c          |   99 ++++++++++
 8 files changed, 494 insertions(+), 2 deletions(-)
---
diff --git a/docs/reference/libedataserver/libedataserver-docs.sgml b/docs/reference/libedataserver/libedataserver-docs.sgml
index 54439b5..c89c9e9 100644
--- a/docs/reference/libedataserver/libedataserver-docs.sgml
+++ b/docs/reference/libedataserver/libedataserver-docs.sgml
@@ -54,6 +54,7 @@
     <xi:include href="xml/e-flag.xml"/>
     <xi:include href="xml/e-memory.xml"/>
     <xi:include href="xml/e-operation-pool.xml"/>
+    <xi:include href="xml/e-phone-utils.xml"/>
     <xi:include href="xml/e-proxy.xml"/>
     <xi:include href="xml/e-sexp.xml"/>
     <xi:include href="xml/e-time-utils.xml"/>
diff --git a/docs/reference/libedataserver/libedataserver-sections.txt b/docs/reference/libedataserver/libedataserver-sections.txt
index a963209..e6382e7 100644
--- a/docs/reference/libedataserver/libedataserver-sections.txt
+++ b/docs/reference/libedataserver/libedataserver-sections.txt
@@ -215,6 +215,22 @@ e_operation_pool_push
 </SECTION>
 
 <SECTION>
+<FILE>e-phone-utils</FILE>
+EPhoneNumber
+EPhoneNumberError
+EPhoneNumberFormat
+e_phone_number_from_string
+e_phone_number_to_string
+e_phone_number_copy
+e_phone_number_free
+<SUBSECTION Standard>
+E_PHONE_NUMBER_ERROR
+E_TYPE_PHONE_NUMBER
+e_phone_number_error_quark
+e_phone_number_get_type
+</SECTION>
+
+<SECTION>
 <FILE>e-proxy</FILE>
 <TITLE>EProxy</TITLE>
 EProxy
diff --git a/libedataserver/Makefile.am b/libedataserver/Makefile.am
index 44111a0..20d8c8e 100644
--- a/libedataserver/Makefile.am
+++ b/libedataserver/Makefile.am
@@ -57,6 +57,7 @@ libedataserver_1_2_la_SOURCES =		\
 	e-list-iterator.c		\
 	e-memory.c			\
 	e-operation-pool.c		\
+	e-phone-utils.cpp		\
 	e-proxy.c			\
 	e-sexp.c			\
 	e-source.c			\
@@ -112,6 +113,11 @@ libedataserver_1_2_la_LDFLAGS = \
 	$(CODE_COVERAGE_LDFLAGS) \
 	$(NULL)
 
+if ENABLE_PHONENUMBER
+libedataserver_1_2_la_CPPFLAGS += $(PHONENUMBER_INCLUDES)
+libedataserver_1_2_la_LIBADD += $(PHONENUMBER_LIBS)
+endif ENABLE_PHONENUMBER
+
 libedataserverincludedir = $(privincludedir)/libedataserver
 
 libedataserverinclude_HEADERS =		\
@@ -127,6 +133,7 @@ libedataserverinclude_HEADERS =		\
 	e-list-iterator.h		\
 	e-memory.h			\
 	e-operation-pool.h		\
+	e-phone-utils.h			\
 	e-proxy.h			\
 	e-sexp.h			\
 	e-source.h			\
diff --git a/libedataserver/e-phone-utils.cpp b/libedataserver/e-phone-utils.cpp
new file mode 100644
index 0000000..de6ff3e
--- /dev/null
+++ b/libedataserver/e-phone-utils.cpp
@@ -0,0 +1,199 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright Copyright (C) 2012 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 <mathias openismus com>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "e-phone-utils.h"
+
+/* GLib headers */
+#include <glib/gi18n-lib.h>
+
+#ifdef ENABLE_PHONENUMBER
+
+/* C++ standard library */
+#include <string>
+
+/* system headers */
+#include <langinfo.h>
+
+/* libphonenumber */
+#include <phonenumbers/logger.h>
+#include <phonenumbers/phonenumberutil.h>
+
+using i18n::phonenumbers::PhoneNumberUtil;
+
+struct _EPhoneNumber {
+	i18n::phonenumbers::PhoneNumber number;
+};
+
+#endif /* ENABLE_PHONENUMBER */
+
+G_DEFINE_BOXED_TYPE (EPhoneNumber,
+                     e_phone_number,
+                     e_phone_number_copy,
+                     e_phone_number_free)
+
+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_INVALID_COUNTRY_CODE:
+		return _("Invalid country code");
+	case E_PHONE_NUMBER_ERROR_NOT_A_NUMBER:
+		return _("Not a phone number");
+	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");
+}
+
+#ifdef ENABLE_PHONENUMBER
+
+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;
+}
+
+#endif /* ENABLE_PHONENUMBER */
+
+EPhoneNumber *
+e_phone_number_from_string (const gchar *phone_number,
+                            const gchar *country_code,
+                            GError **error)
+{
+	EPhoneNumber *parsed_number = NULL;
+
+	g_return_val_if_fail (NULL != phone_number, NULL);
+
+#ifdef ENABLE_PHONENUMBER
+
+	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 */
+	}
+
+	parsed_number = new EPhoneNumber;
+
+	const PhoneNumberUtil::ErrorType err =
+		e_phone_number_util_get_instance ()->Parse (phone_number, country_code,
+		                                            &parsed_number->number);
+
+	if (err != PhoneNumberUtil::NO_PARSING_ERROR) {
+		const EPhoneNumberError code = static_cast<EPhoneNumberError> (err);
+
+		g_set_error_literal (error, E_PHONE_NUMBER_ERROR, err,
+		                     e_phone_number_error_to_string (code));
+
+		delete parsed_number;
+		parsed_number = NULL;
+	}
+
+#else /* ENABLE_PHONENUMBER */
+
+	g_critical ("%s: This function is not available because phone number "
+	            "support was disabled when building %s.", G_STRFUNC, PACKAGE);
+
+#endif /* ENABLE_PHONENUMBER */
+
+	return parsed_number;
+}
+
+gchar *
+e_phone_number_to_string (const EPhoneNumber *phone_number,
+                          EPhoneNumberFormat format)
+{
+	g_return_val_if_fail (NULL != phone_number, NULL);
+
+#ifdef ENABLE_PHONENUMBER
+
+	std::string formatted_number;
+
+	e_phone_number_util_get_instance ()->Format
+		(phone_number->number,
+		 static_cast<PhoneNumberUtil::PhoneNumberFormat> (format),
+		 &formatted_number);
+
+	if (!formatted_number.empty ())
+		return g_strdup (formatted_number.c_str ());
+
+#else /* ENABLE_PHONENUMBER */
+
+	g_critical ("%s: This function is not available because phone number "
+	            "support was disabled when building %s.", G_STRFUNC, PACKAGE);
+
+#endif /* ENABLE_PHONENUMBER */
+
+	return NULL;
+}
+
+EPhoneNumber *
+e_phone_number_copy (const EPhoneNumber *phone_number)
+{
+#ifdef ENABLE_PHONENUMBER
+	if (phone_number)
+		return new EPhoneNumber (*phone_number);
+#endif /* ENABLE_PHONENUMBER */
+
+	return NULL;
+}
+
+void
+e_phone_number_free (EPhoneNumber *phone_number)
+{
+#ifdef ENABLE_PHONENUMBER
+	delete phone_number;
+#endif /* ENABLE_PHONENUMBER */
+}
diff --git a/libedataserver/e-phone-utils.h b/libedataserver/e-phone-utils.h
new file mode 100644
index 0000000..a02d73c
--- /dev/null
+++ b/libedataserver/e-phone-utils.h
@@ -0,0 +1,160 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright Copyright (C) 2012 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 <mathias openismus com>
+ */
+
+/* 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 (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#ifndef E_PHONE_UTILS_H
+#define E_PHONE_UTILS_H
+
+#include <glib-object.h>
+
+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;
+
+/**
+ * EPhoneNumberError:
+ * @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_INVALID_COUNTRY_CODE = 1,
+	E_PHONE_NUMBER_ERROR_NOT_A_NUMBER,
+	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) G_GNUC_CONST;
+GQuark			e_phone_number_error_quark	(void) G_GNUC_CONST;
+
+/**
+ * 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. 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+G_END_DECLS
+
+#endif /* E_BOOK_BACKEND_FILE_PHONE_UTILS_H */
diff --git a/libedataserver/libedataserver.h b/libedataserver/libedataserver.h
index ca98003..8ee2993 100644
--- a/libedataserver/libedataserver.h
+++ b/libedataserver/libedataserver.h
@@ -34,6 +34,7 @@
 #include <libedataserver/e-list.h>
 #include <libedataserver/e-memory.h>
 #include <libedataserver/e-operation-pool.h>
+#include <libedataserver/e-phone-utils.h>
 #include <libedataserver/e-proxy.h>
 #include <libedataserver/e-sexp.h>
 #include <libedataserver/e-source-address-book.h>
diff --git a/tests/libedataserver/Makefile.am b/tests/libedataserver/Makefile.am
index 2019aa8..37bae46 100644
--- a/tests/libedataserver/Makefile.am
+++ b/tests/libedataserver/Makefile.am
@@ -2,16 +2,25 @@ TESTS = e-source-test
 
 noinst_PROGRAMS = $(TESTS)
 
-e_source_test_CPPFLAGS =					\
+TEST_CPPFLAGS =							\
 	$(AM_CPPFLAGS)						\
 	-I$(top_srcdir)						\
 	-DG_LOG_DOMAIN=\"e-data-server\"			\
 	$(E_DATA_SERVER_CFLAGS)					\
 	$(GIO_UNIX_CFLAGS)
 
-e_source_test_LDADD =						\
+TEST_LDADD =							\
 	$(top_builddir)/libedataserver/libedataserver-1.2.la	\
 	$(E_DATA_SERVER_LIBS)					\
 	$(GIO_UNIX_LIBS)
 
+e_source_test_CPPFLAGS = $(TEST_CPPFLAGS)
+e_source_test_LDADD = $(TEST_LDADD)
+
+if ENABLE_PHONENUMBER
+TESTS += e-phone-utils-test
+e_phone_utils_test_CPPFLAGS = $(TEST_CPPFLAGS)
+e_phone_utils_test_LDADD = $(TEST_LDADD)
+endif ENABLE_PHONENUMBER
+
 -include $(top_srcdir)/git.mk
diff --git a/tests/libedataserver/e-phone-utils-test.c b/tests/libedataserver/e-phone-utils-test.c
new file mode 100644
index 0000000..fe58a08
--- /dev/null
+++ b/tests/libedataserver/e-phone-utils-test.c
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright Copyright (C) 2012 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 <mathias openismus com>
+ */
+
+#include <libedataserver/libedataserver.h>
+
+static void
+test_parse_and_format (gconstpointer data)
+{
+	GError *error = NULL;
+	EPhoneNumber *parsed;
+	gchar **test_numbers;
+	gchar **params;
+	gchar *formatted;
+	gint i;
+
+	params = g_strsplit (data, "/", G_MAXINT);
+	g_assert_cmpint (g_strv_length (params), ==, 6);
+	test_numbers = params + 2;
+
+	parsed = e_phone_number_from_string (params[0], params[1], &error);
+	g_assert (parsed != NULL);
+	g_assert (error == NULL);
+
+	for (i = 0; test_numbers[i]; ++i) {
+		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);
+	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);
+	g_assert (error->code == E_PHONE_NUMBER_ERROR_NOT_A_NUMBER);
+	g_assert (error->message != NULL);
+
+	g_error_free (error);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+	g_type_init ();
+
+	g_test_init (&argc, &argv, NULL);
+
+	g_test_add_data_func
+		("/e-phone-utils-test/ParseAndFormat/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",
+		 "(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",
+		 "+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",
+		 "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",
+		 test_parse_bad_number);
+
+	return g_test_run ();
+}



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