blob: 775e33f8f503f7d49908b4ca33cb5c08cd23660d [file] [log] [blame]
/*
* Copyright 2009, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ctype.h>
#include <string.h>
namespace android {
/* Generated by the following Python script. Values of country calling codes
are from http://en.wikipedia.org/wiki/List_of_country_calling_codes
#!/usr/bin/python
import sys
ccc_set_2digits = set([0, 1, 7,
20, 27, 28, 30, 31, 32, 33, 34, 36, 39, 40, 43, 44, 45,
46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61,
62, 63, 64, 65, 66, 81, 82, 83, 84, 86, 89, 90, 91, 92,
93, 94, 95, 98])
ONE_LINE_NUM = 10
for i in xrange(100):
if i % ONE_LINE_NUM == 0:
sys.stdout.write(' ')
if i in ccc_set_2digits:
included = 'true'
else:
included = 'false'
sys.stdout.write(included + ',')
if ((i + 1) % ONE_LINE_NUM) == 0:
sys.stdout.write('\n')
else:
sys.stdout.write(' ')
*/
static bool two_length_country_code_map[100] = {
true, true, false, false, false, false, false, true, false, false,
false, false, false, false, false, false, false, false, false, false,
true, false, false, false, false, false, false, true, true, false,
true, true, true, true, true, false, true, false, false, true,
true, false, false, true, true, true, true, true, true, true,
false, true, true, true, true, true, true, true, true, false,
true, true, true, true, true, true, true, false, false, false,
false, false, false, false, false, false, false, false, false, false,
false, true, true, true, true, false, true, false, false, true,
true, true, true, true, true, true, false, false, true, false,
};
#define ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
/**
* Returns true if "ccc_candidate" expresses (part of ) some country calling
* code.
* Returns false otherwise.
*/
static bool isCountryCallingCode(int ccc_candidate) {
return ccc_candidate > 0 &&
ccc_candidate < (int)ARRAY_SIZE(two_length_country_code_map) &&
two_length_country_code_map[ccc_candidate];
}
/**
* Returns interger corresponding to the input if input "ch" is
* ISO-LATIN characters 0-9.
* Returns -1 otherwise
*/
static int tryGetISODigit (char ch)
{
if ('0' <= ch && ch <= '9') {
return ch - '0';
} else {
return -1;
}
}
/**
* True if ch is ISO-LATIN characters 0-9, *, # , +
* Note this method current does not account for the WILD char 'N'
*/
static bool isDialable(char ch)
{
return ('0' <= ch && ch <= '9') || ch == '*' || ch == '#' || ch == '+';
}
/** Returns true if ch is not dialable or alpha char */
static bool isSeparator(char ch)
{
return !isDialable(ch) && (isalpha(ch) == 0);
}
/**
* Try to store the pointer to "new_ptr" which does not have trunk prefix.
*
* Currently this function simply ignore the first digit assuming it is
* trunk prefix. Actually trunk prefix is different in each country.
*
* e.g.
* "+79161234567" equals "89161234567" (Russian trunk digit is 8)
* "+33123456789" equals "0123456789" (French trunk digit is 0)
*
*/
static bool tryGetTrunkPrefixOmittedStr(const char *str, size_t len,
const char **new_ptr, size_t *new_len)
{
for (size_t i = 0 ; i < len ; i++) {
char ch = str[i];
if (tryGetISODigit(ch) >= 0) {
if (new_ptr != NULL) {
*new_ptr = str + i + 1;
}
if (new_len != NULL) {
*new_len = len - (i + 1);
}
return true;
} else if (isDialable(ch)) {
return false;
}
}
return false;
}
/*
* Note that this function does not strictly care the country calling code with
* 3 length (like Morocco: +212), assuming it is enough to use the first two
* digit to compare two phone numbers.
*/
static int tryGetCountryCallingCode(const char *str, size_t len,
const char **new_ptr, size_t *new_len,
bool accept_thailand_case)
{
// Rough regexp:
// ^[^0-9*#+]*((\+|0(0|11)\d\d?|166) [^0-9*#+] $
// 0 1 2 3 45 6 7 89
//
// In all the states, this function ignores separator characters.
// "166" is the special case for the call from Thailand to the US. Ugu!
int state = 0;
int ccc = 0;
for (size_t i = 0 ; i < len ; i++ ) {
char ch = str[i];
switch (state) {
case 0:
if (ch == '+') state = 1;
else if (ch == '0') state = 2;
else if (ch == '1') {
if (accept_thailand_case) {
state = 8;
} else {
return -1;
}
} else if (isDialable(ch)) return -1;
break;
case 2:
if (ch == '0') state = 3;
else if (ch == '1') state = 4;
else if (isDialable(ch)) return -1;
break;
case 4:
if (ch == '1') state = 5;
else if (isDialable(ch)) return -1;
break;
case 1:
case 3:
case 5:
case 6:
case 7:
{
int ret = tryGetISODigit(ch);
if (ret > 0) {
ccc = ccc * 10 + ret;
if (ccc >= 100 || isCountryCallingCode(ccc)) {
if (new_ptr != NULL) {
*new_ptr = str + i + 1;
}
if (new_len != NULL) {
*new_len = len - (i + 1);
}
return ccc;
}
if (state == 1 || state == 3 || state == 5) {
state = 6;
} else {
state++;
}
} else if (isDialable(ch)) {
return -1;
}
}
break;
case 8:
if (ch == '6') state = 9;
else if (isDialable(ch)) return -1;
break;
case 9:
if (ch == '6') {
if (new_ptr != NULL) {
*new_ptr = str + i + 1;
}
if (new_len != NULL) {
*new_len = len - (i + 1);
}
return 66;
} else {
return -1;
}
break;
default:
return -1;
}
}
return -1;
}
/**
* Return true if the prefix of "ch" is "ignorable". Here, "ignorable" means
* that "ch" has only one digit and separater characters. The one digit is
* assumed to be trunk prefix.
*/
static bool checkPrefixIsIgnorable(const char* ch, int i) {
bool trunk_prefix_was_read = false;
while (i >= 0) {
if (tryGetISODigit(ch[i]) >= 0) {
if (trunk_prefix_was_read) {
// More than one digit appeared, meaning that "a" and "b"
// is different.
return false;
} else {
// Ignore just one digit, assuming it is trunk prefix.
trunk_prefix_was_read = true;
}
} else if (isDialable(ch[i])) {
// Trunk prefix is a digit, not "*", "#"...
return false;
}
i--;
}
return true;
}
/**
* Compare phone numbers a and b, return true if they're identical
* enough for caller ID purposes.
*
* Assume NULL as 0-length string.
*
* Detailed information:
* Currently (as of 2009-06-12), we cannot depend on the locale given from the
* OS. For example, current Android does not accept "en_JP", meaning
* "the display language is English but the phone should be in Japan", but
* en_US, es_US, etc. So we cannot identify which digit is valid trunk prefix
* in the country where the phone is used. More specifically, "880-1234-1234"
* is not valid phone number in Japan since the trunk prefix in Japan is not 8
* but 0 (correct number should be "080-1234-1234"), while Russian trunk prefix
* is 8. Also, we cannot know whether the country where users live has trunk
* prefix itself. So, we cannot determine whether "+81-80-1234-1234" is NOT
* same as "880-1234-1234" (while "+81-80-1234-1234" is same as "080-1234-1234"
* and we can determine "880-1234-1234" is different from "080-1234-1234").
*
* In the future, we should handle trunk prefix more correctly, but as of now,
* we just ignore it...
*/
static bool phone_number_compare_inter(const char* const org_a, const char* const org_b,
bool accept_thailand_case)
{
const char* a = org_a;
const char* b = org_b;
size_t len_a = 0;
size_t len_b = 0;
if (a == NULL) {
a = "";
} else {
len_a = strlen(a);
}
if (b == NULL) {
b = "";
} else {
len_b = strlen(b);
}
const char* tmp_a = NULL;
const char* tmp_b = NULL;
size_t tmp_len_a = len_a;
size_t tmp_len_b = len_b;
int ccc_a = tryGetCountryCallingCode(a, len_a, &tmp_a, &tmp_len_a, accept_thailand_case);
int ccc_b = tryGetCountryCallingCode(b, len_b, &tmp_b, &tmp_len_b, accept_thailand_case);
bool both_have_ccc = false;
bool ok_to_ignore_prefix = true;
bool trunk_prefix_is_omitted_a = false;
bool trunk_prefix_is_omitted_b = false;
if (ccc_a >= 0 && ccc_b >= 0) {
if (ccc_a != ccc_b) {
// Different Country Calling Code. Must be different phone number.
return false;
}
// When both have ccc, do not ignore trunk prefix. Without this,
// "+81123123" becomes same as "+810123123" (+81 == Japan)
ok_to_ignore_prefix = false;
both_have_ccc = true;
} else if (ccc_a < 0 && ccc_b < 0) {
// When both do not have ccc, do not ignore trunk prefix. Without this,
// "123123" becomes same as "0123123"
ok_to_ignore_prefix = false;
} else {
if (ccc_a < 0) {
tryGetTrunkPrefixOmittedStr(a, len_a, &tmp_a, &tmp_len_a);
trunk_prefix_is_omitted_a = true;
}
if (ccc_b < 0) {
tryGetTrunkPrefixOmittedStr(b, len_b, &tmp_b, &tmp_len_b);
trunk_prefix_is_omitted_b = true;
}
}
if (tmp_a != NULL) {
a = tmp_a;
len_a = tmp_len_a;
}
if (tmp_b != NULL) {
b = tmp_b;
len_b = tmp_len_b;
}
int i_a = len_a - 1;
int i_b = len_b - 1;
while (i_a >= 0 && i_b >= 0) {
bool skip_compare = false;
char ch_a = a[i_a];
char ch_b = b[i_b];
if (isSeparator(ch_a)) {
i_a--;
skip_compare = true;
}
if (isSeparator(ch_b)) {
i_b--;
skip_compare = true;
}
if (!skip_compare) {
if (ch_a != ch_b) {
return false;
}
i_a--;
i_b--;
}
}
if (ok_to_ignore_prefix) {
if ((trunk_prefix_is_omitted_a && i_a >= 0) ||
!checkPrefixIsIgnorable(a, i_a)) {
if (accept_thailand_case) {
// Maybe the code handling the special case for Thailand makes the
// result garbled, so disable the code and try again.
// e.g. "16610001234" must equal to "6610001234", but with
// Thailand-case handling code, they become equal to each other.
//
// Note: we select simplicity rather than adding some complicated
// logic here for performance(like "checking whether remaining
// numbers are just 66 or not"), assuming inputs are small
// enough.
return phone_number_compare_inter(org_a, org_b, false);
} else {
return false;
}
}
if ((trunk_prefix_is_omitted_b && i_b >= 0) ||
!checkPrefixIsIgnorable(b, i_b)) {
if (accept_thailand_case) {
return phone_number_compare_inter(org_a, org_b, false);
} else {
return false;
}
}
} else {
// In the US, 1-650-555-1234 must be equal to 650-555-1234,
// while 090-1234-1234 must not be equalt to 90-1234-1234 in Japan.
// This request exists just in US (with 1 trunk (NDD) prefix).
// In addition, "011 11 7005554141" must not equal to "+17005554141",
// while "011 1 7005554141" must equal to "+17005554141"
//
// In this comparison, we ignore the prefix '1' just once, when
// - at least either does not have CCC, or
// - the remaining non-separator number is 1
bool may_be_namp = !both_have_ccc;
while (i_a >= 0) {
const char ch_a = a[i_a];
if (isDialable(ch_a)) {
if (may_be_namp && tryGetISODigit(ch_a) == 1) {
may_be_namp = false;
} else {
return false;
}
}
i_a--;
}
while (i_b >= 0) {
const char ch_b = b[i_b];
if (isDialable(ch_b)) {
if (may_be_namp && tryGetISODigit(ch_b) == 1) {
may_be_namp = false;
} else {
return false;
}
}
i_b--;
}
}
return true;
}
bool phone_number_compare_strict(const char* a, const char* b)
{
return phone_number_compare_inter(a, b, true);
}
} // namespace android