|
|
@ -35,6 +35,7 @@ |
|
|
#include "base/logging.h"
|
|
|
#include "base/logging.h"
|
|
|
#include "base/memory/scoped_ptr.h"
|
|
|
#include "base/memory/scoped_ptr.h"
|
|
|
#include "base/memory/singleton.h"
|
|
|
#include "base/memory/singleton.h"
|
|
|
|
|
|
#include "phonenumbers/callback.h"
|
|
|
#include "phonenumbers/default_logger.h"
|
|
|
#include "phonenumbers/default_logger.h"
|
|
|
#include "phonenumbers/encoding_utils.h"
|
|
|
#include "phonenumbers/encoding_utils.h"
|
|
|
#include "phonenumbers/normalize_utf8.h"
|
|
|
#include "phonenumbers/normalize_utf8.h"
|
|
|
@ -70,28 +71,6 @@ bool IsInvalidPunctuationSymbol(char32 character) { |
|
|
return character == '%' || u_charType(character) == U_CURRENCY_SYMBOL; |
|
|
return character == '%' || u_charType(character) == U_CURRENCY_SYMBOL; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Helper method to get the national-number part of a number, formatted without
|
|
|
|
|
|
// any national prefix, and return it as a set of digit blocks that would be
|
|
|
|
|
|
// formatted together.
|
|
|
|
|
|
void GetNationalNumberGroups(const PhoneNumberUtil& util, |
|
|
|
|
|
const PhoneNumber& number, |
|
|
|
|
|
vector<string>* digit_blocks) { |
|
|
|
|
|
// This will be in the format +CC-DG;ext=EXT where DG represents groups of
|
|
|
|
|
|
// digits.
|
|
|
|
|
|
string rfc3966_format; |
|
|
|
|
|
util.Format(number, PhoneNumberUtil::RFC3966, &rfc3966_format); |
|
|
|
|
|
// We remove the extension part from the formatted string before splitting it
|
|
|
|
|
|
// into different groups.
|
|
|
|
|
|
size_t end_index = rfc3966_format.find(';'); |
|
|
|
|
|
if (end_index == string::npos) { |
|
|
|
|
|
end_index = rfc3966_format.length(); |
|
|
|
|
|
} |
|
|
|
|
|
// The country-code will have a '-' following it.
|
|
|
|
|
|
size_t start_index = rfc3966_format.find('-') + 1; |
|
|
|
|
|
SplitStringUsing(rfc3966_format.substr(start_index, end_index - start_index), |
|
|
|
|
|
"-", digit_blocks); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool ContainsOnlyValidXChars(const PhoneNumber& number, const string& candidate, |
|
|
bool ContainsOnlyValidXChars(const PhoneNumber& number, const string& candidate, |
|
|
const PhoneNumberUtil& util) { |
|
|
const PhoneNumberUtil& util) { |
|
|
// The characters 'x' and 'X' can be (1) a carrier code, in which case they
|
|
|
// The characters 'x' and 'X' can be (1) a carrier code, in which case they
|
|
|
@ -128,6 +107,48 @@ bool ContainsOnlyValidXChars(const PhoneNumber& number, const string& candidate, |
|
|
} |
|
|
} |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool AllNumberGroupsRemainGrouped( |
|
|
|
|
|
const PhoneNumberUtil& util, |
|
|
|
|
|
const PhoneNumber& phone_number, |
|
|
|
|
|
const string& normalized_candidate, |
|
|
|
|
|
const vector<string>& formatted_number_groups) { |
|
|
|
|
|
size_t from_index = 0; |
|
|
|
|
|
// Check each group of consecutive digits are not broken into separate
|
|
|
|
|
|
// groupings in the normalized_candidate string.
|
|
|
|
|
|
for (size_t i = 0; i < formatted_number_groups.size(); ++i) { |
|
|
|
|
|
// Fails if the substring of normalized_candidate starting from from_index
|
|
|
|
|
|
// doesn't contain the consecutive digits in formatted_number_groups.at(i).
|
|
|
|
|
|
from_index = normalized_candidate.find(formatted_number_groups.at(i), |
|
|
|
|
|
from_index); |
|
|
|
|
|
if (from_index == string::npos) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
// Moves from_index forward.
|
|
|
|
|
|
from_index += formatted_number_groups.at(i).length(); |
|
|
|
|
|
if (i == 0 && from_index < normalized_candidate.length()) { |
|
|
|
|
|
// We are at the position right after the NDC. Note although
|
|
|
|
|
|
// normalized_candidate might contain non-ASCII formatting characters,
|
|
|
|
|
|
// they won't be treated as ASCII digits when converted to a char.
|
|
|
|
|
|
if (isdigit(normalized_candidate.at(from_index))) { |
|
|
|
|
|
// This means there is no formatting symbol after the NDC. In this case,
|
|
|
|
|
|
// we only accept the number if there is no formatting symbol at all in
|
|
|
|
|
|
// the number, except for extensions.
|
|
|
|
|
|
string national_significant_number; |
|
|
|
|
|
util.GetNationalSignificantNumber( |
|
|
|
|
|
phone_number, &national_significant_number); |
|
|
|
|
|
return HasPrefixString(normalized_candidate.substr( |
|
|
|
|
|
from_index - formatted_number_groups.at(i).length()), |
|
|
|
|
|
national_significant_number); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// The check here makes sure that we haven't mistakenly already used the
|
|
|
|
|
|
// extension to match the last group of the subscriber number. Note the
|
|
|
|
|
|
// extension cannot have formatting in-between digits.
|
|
|
|
|
|
return normalized_candidate.substr(from_index) |
|
|
|
|
|
.find(phone_number.extension()) != string::npos; |
|
|
|
|
|
} |
|
|
} // namespace
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
#ifdef USE_GOOGLE_BASE
|
|
|
#ifdef USE_GOOGLE_BASE
|
|
|
@ -411,49 +432,12 @@ bool PhoneNumberMatcher::VerifyAccordingToLeniency( |
|
|
!IsNationalPrefixPresentIfRequired(number)) { |
|
|
!IsNationalPrefixPresentIfRequired(number)) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
// TODO(lararennie,shaopengjia): Evaluate how this works for other locales
|
|
|
|
|
|
// (testing has been limited to NANPA regions) and optimise if necessary.
|
|
|
|
|
|
string normalized_candidate = |
|
|
|
|
|
NormalizeUTF8::NormalizeDecimalDigits(candidate); |
|
|
|
|
|
vector<string> formatted_number_groups; |
|
|
|
|
|
GetNationalNumberGroups(phone_util_, number, &formatted_number_groups); |
|
|
|
|
|
size_t from_index = 0; |
|
|
|
|
|
// Check each group of consecutive digits are not broken into separate
|
|
|
|
|
|
// groups in the normalized_candidate string.
|
|
|
|
|
|
for (size_t i = 0; i < formatted_number_groups.size(); ++i) { |
|
|
|
|
|
// Fails if the substring of normalized_candidate starting from
|
|
|
|
|
|
// from_index doesn't contain the consecutive digits in digit_group.
|
|
|
|
|
|
from_index = normalized_candidate.find(formatted_number_groups.at(i), |
|
|
|
|
|
from_index); |
|
|
|
|
|
if (from_index == string::npos) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
// Moves from_index forward.
|
|
|
|
|
|
from_index += formatted_number_groups.at(i).length(); |
|
|
|
|
|
if (i == 0 && from_index < normalized_candidate.length()) { |
|
|
|
|
|
// We are at the position right after the NDC. Note although
|
|
|
|
|
|
// normalized_candidate might contain non-ASCII formatting characters,
|
|
|
|
|
|
// they won't be treated as ASCII digits when converted to a char.
|
|
|
|
|
|
if (isdigit(normalized_candidate.at(from_index))) { |
|
|
|
|
|
// This means there is no formatting symbol after the NDC. In this
|
|
|
|
|
|
// case, we only accept the number if there is no formatting
|
|
|
|
|
|
// symbol at all in the number, except for extensions.
|
|
|
|
|
|
string national_significant_number; |
|
|
|
|
|
phone_util_.GetNationalSignificantNumber( |
|
|
|
|
|
number, &national_significant_number); |
|
|
|
|
|
return HasPrefixString( |
|
|
|
|
|
normalized_candidate.substr( |
|
|
|
|
|
from_index - formatted_number_groups.at(i).length()), |
|
|
|
|
|
national_significant_number); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// The check here makes sure that we haven't mistakenly already used the
|
|
|
|
|
|
// extension to match the last group of the subscriber number. Note the
|
|
|
|
|
|
// extension cannot have formatting in-between digits.
|
|
|
|
|
|
return |
|
|
|
|
|
normalized_candidate.substr(from_index).find(number.extension()) != |
|
|
|
|
|
string::npos; |
|
|
|
|
|
|
|
|
ResultCallback4<bool, const PhoneNumberUtil&, const PhoneNumber&, |
|
|
|
|
|
const string&, const vector<string>&>* callback = |
|
|
|
|
|
NewPermanentCallback(&AllNumberGroupsRemainGrouped); |
|
|
|
|
|
bool is_valid = CheckNumberGroupingIsValid(number, candidate, callback); |
|
|
|
|
|
delete(callback); |
|
|
|
|
|
return is_valid; |
|
|
} |
|
|
} |
|
|
case PhoneNumberMatcher::EXACT_GROUPING: { |
|
|
case PhoneNumberMatcher::EXACT_GROUPING: { |
|
|
if (!phone_util_.IsValidNumber(number) || |
|
|
if (!phone_util_.IsValidNumber(number) || |
|
|
@ -463,56 +447,13 @@ bool PhoneNumberMatcher::VerifyAccordingToLeniency( |
|
|
!IsNationalPrefixPresentIfRequired(number)) { |
|
|
!IsNationalPrefixPresentIfRequired(number)) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
// TODO(lararennie,shaopengjia): Evaluate how this works for other locales
|
|
|
|
|
|
// (testing has been limited to NANPA regions) and optimise if necessary.
|
|
|
|
|
|
vector<string> candidate_groups; |
|
|
|
|
|
string normalized_candidate = |
|
|
|
|
|
NormalizeUTF8::NormalizeDecimalDigits(candidate); |
|
|
|
|
|
const scoped_ptr<RegExpInput> candidate_number( |
|
|
|
|
|
reg_exps_->regexp_factory_->CreateInput(normalized_candidate)); |
|
|
|
|
|
string digit_block; |
|
|
|
|
|
while (reg_exps_->capturing_ascii_digits_pattern_->FindAndConsume( |
|
|
|
|
|
candidate_number.get(), |
|
|
|
|
|
&digit_block)) { |
|
|
|
|
|
candidate_groups.push_back(digit_block); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Set this to the last group, skipping it if the number has an extension.
|
|
|
|
|
|
int candidate_number_group_index = |
|
|
|
|
|
number.has_extension() ? candidate_groups.size() - 2 |
|
|
|
|
|
: candidate_groups.size() - 1; |
|
|
|
|
|
// First we check if the national significant number is formatted as a
|
|
|
|
|
|
// block. We use contains and not equals, since the national significant
|
|
|
|
|
|
// number may be present with a prefix such as a national number prefix,
|
|
|
|
|
|
// or the country code itself.
|
|
|
|
|
|
string national_significant_number; |
|
|
|
|
|
phone_util_.GetNationalSignificantNumber(number, |
|
|
|
|
|
&national_significant_number); |
|
|
|
|
|
if (candidate_groups.size() == 1 || |
|
|
|
|
|
candidate_groups.at(candidate_number_group_index).find( |
|
|
|
|
|
national_significant_number) != string::npos) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
vector<string> formatted_number_groups; |
|
|
|
|
|
GetNationalNumberGroups(phone_util_, number, &formatted_number_groups); |
|
|
|
|
|
// Starting from the end, go through in reverse, excluding the first
|
|
|
|
|
|
// group, and check the candidate and number groups are the same.
|
|
|
|
|
|
for (int formatted_number_group_index = |
|
|
|
|
|
(formatted_number_groups.size() - 1); |
|
|
|
|
|
formatted_number_group_index > 0 && |
|
|
|
|
|
candidate_number_group_index >= 0; |
|
|
|
|
|
--formatted_number_group_index, --candidate_number_group_index) { |
|
|
|
|
|
if (candidate_groups.at(candidate_number_group_index) != |
|
|
|
|
|
formatted_number_groups.at(formatted_number_group_index)) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// Now check the first group. There may be a national prefix at the start,
|
|
|
|
|
|
// so we only check that the candidate group ends with the formatted
|
|
|
|
|
|
// number group.
|
|
|
|
|
|
return (candidate_number_group_index >= 0 && |
|
|
|
|
|
HasSuffixString(candidate_groups.at(candidate_number_group_index), |
|
|
|
|
|
formatted_number_groups.at(0))); |
|
|
|
|
|
|
|
|
ResultCallback4<bool, const PhoneNumberUtil&, const PhoneNumber&, |
|
|
|
|
|
const string&, const vector<string>&>* callback = |
|
|
|
|
|
NewPermanentCallback( |
|
|
|
|
|
this, &PhoneNumberMatcher::AllNumberGroupsAreExactlyPresent); |
|
|
|
|
|
bool is_valid = CheckNumberGroupingIsValid(number, candidate, callback); |
|
|
|
|
|
delete(callback); |
|
|
|
|
|
return is_valid; |
|
|
} |
|
|
} |
|
|
default: |
|
|
default: |
|
|
LOG(ERROR) << "No implementation defined for verification for leniency " |
|
|
LOG(ERROR) << "No implementation defined for verification for leniency " |
|
|
@ -655,6 +596,62 @@ bool PhoneNumberMatcher::Find(int index, PhoneNumberMatch* match) { |
|
|
return false; |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool PhoneNumberMatcher::CheckNumberGroupingIsValid( |
|
|
|
|
|
const PhoneNumber& phone_number, |
|
|
|
|
|
const string& candidate, |
|
|
|
|
|
ResultCallback4<bool, const PhoneNumberUtil&, const PhoneNumber&, |
|
|
|
|
|
const string&, const vector<string>&>* checker) const { |
|
|
|
|
|
DCHECK(checker); |
|
|
|
|
|
// TODO: Evaluate how this works for other locales (testing has been limited
|
|
|
|
|
|
// to NANPA regions) and optimise if necessary.
|
|
|
|
|
|
string normalized_candidate = |
|
|
|
|
|
NormalizeUTF8::NormalizeDecimalDigits(candidate); |
|
|
|
|
|
vector<string> formatted_number_groups; |
|
|
|
|
|
GetNationalNumberGroups(phone_number, NULL, // Use default formatting pattern
|
|
|
|
|
|
&formatted_number_groups); |
|
|
|
|
|
if (checker->Run(phone_util_, phone_number, normalized_candidate, |
|
|
|
|
|
formatted_number_groups)) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Helper method to get the national-number part of a number, formatted without
|
|
|
|
|
|
// any national prefix, and return it as a set of digit blocks that would be
|
|
|
|
|
|
// formatted together.
|
|
|
|
|
|
void PhoneNumberMatcher::GetNationalNumberGroups( |
|
|
|
|
|
const PhoneNumber& number, |
|
|
|
|
|
const NumberFormat* formatting_pattern, |
|
|
|
|
|
vector<string>* digit_blocks) const { |
|
|
|
|
|
string rfc3966_format; |
|
|
|
|
|
if (!formatting_pattern) { |
|
|
|
|
|
// This will be in the format +CC-DG;ext=EXT where DG represents groups of
|
|
|
|
|
|
// digits.
|
|
|
|
|
|
phone_util_.Format(number, PhoneNumberUtil::RFC3966, &rfc3966_format); |
|
|
|
|
|
// We remove the extension part from the formatted string before splitting
|
|
|
|
|
|
// it into different groups.
|
|
|
|
|
|
size_t end_index = rfc3966_format.find(';'); |
|
|
|
|
|
if (end_index == string::npos) { |
|
|
|
|
|
end_index = rfc3966_format.length(); |
|
|
|
|
|
} |
|
|
|
|
|
// The country-code will have a '-' following it.
|
|
|
|
|
|
size_t start_index = rfc3966_format.find('-') + 1; |
|
|
|
|
|
SplitStringUsing(rfc3966_format.substr(start_index, |
|
|
|
|
|
end_index - start_index), |
|
|
|
|
|
"-", digit_blocks); |
|
|
|
|
|
} else { |
|
|
|
|
|
// We format the NSN only, and split that according to the separator.
|
|
|
|
|
|
string national_significant_number; |
|
|
|
|
|
phone_util_.GetNationalSignificantNumber(number, |
|
|
|
|
|
&national_significant_number); |
|
|
|
|
|
phone_util_.FormatNsnUsingPattern(national_significant_number, |
|
|
|
|
|
*formatting_pattern, |
|
|
|
|
|
PhoneNumberUtil::RFC3966, |
|
|
|
|
|
&rfc3966_format); |
|
|
|
|
|
SplitStringUsing(rfc3966_format, "-", digit_blocks); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
bool PhoneNumberMatcher::IsNationalPrefixPresentIfRequired( |
|
|
bool PhoneNumberMatcher::IsNationalPrefixPresentIfRequired( |
|
|
const PhoneNumber& number) const { |
|
|
const PhoneNumber& number) const { |
|
|
// First, check how we deduced the country code. If it was written in
|
|
|
// First, check how we deduced the country code. If it was written in
|
|
|
@ -709,5 +706,56 @@ bool PhoneNumberMatcher::IsNationalPrefixPresentIfRequired( |
|
|
return true; |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bool PhoneNumberMatcher::AllNumberGroupsAreExactlyPresent( |
|
|
|
|
|
const PhoneNumberUtil& util, |
|
|
|
|
|
const PhoneNumber& phone_number, |
|
|
|
|
|
const string& normalized_candidate, |
|
|
|
|
|
const vector<string>& formatted_number_groups) const { |
|
|
|
|
|
const scoped_ptr<RegExpInput> candidate_number( |
|
|
|
|
|
reg_exps_->regexp_factory_->CreateInput(normalized_candidate)); |
|
|
|
|
|
vector<string> candidate_groups; |
|
|
|
|
|
string digit_block; |
|
|
|
|
|
while (reg_exps_->capturing_ascii_digits_pattern_->FindAndConsume( |
|
|
|
|
|
candidate_number.get(), |
|
|
|
|
|
&digit_block)) { |
|
|
|
|
|
candidate_groups.push_back(digit_block); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Set this to the last group, skipping it if the number has an extension.
|
|
|
|
|
|
int candidate_number_group_index = |
|
|
|
|
|
phone_number.has_extension() ? candidate_groups.size() - 2 |
|
|
|
|
|
: candidate_groups.size() - 1; |
|
|
|
|
|
// First we check if the national significant number is formatted as a block.
|
|
|
|
|
|
// We use find and not equals, since the national significant number may be
|
|
|
|
|
|
// present with a prefix such as a national number prefix, or the country code
|
|
|
|
|
|
// itself.
|
|
|
|
|
|
string national_significant_number; |
|
|
|
|
|
util.GetNationalSignificantNumber(phone_number, |
|
|
|
|
|
&national_significant_number); |
|
|
|
|
|
if (candidate_groups.size() == 1 || |
|
|
|
|
|
candidate_groups.at(candidate_number_group_index).find( |
|
|
|
|
|
national_significant_number) != string::npos) { |
|
|
|
|
|
return true; |
|
|
|
|
|
} |
|
|
|
|
|
// Starting from the end, go through in reverse, excluding the first group,
|
|
|
|
|
|
// and check the candidate and number groups are the same.
|
|
|
|
|
|
for (int formatted_number_group_index = |
|
|
|
|
|
(formatted_number_groups.size() - 1); |
|
|
|
|
|
formatted_number_group_index > 0 && |
|
|
|
|
|
candidate_number_group_index >= 0; |
|
|
|
|
|
--formatted_number_group_index, --candidate_number_group_index) { |
|
|
|
|
|
if (candidate_groups.at(candidate_number_group_index) != |
|
|
|
|
|
formatted_number_groups.at(formatted_number_group_index)) { |
|
|
|
|
|
return false; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// Now check the first group. There may be a national prefix at the start, so
|
|
|
|
|
|
// we only check that the candidate group ends with the formatted number
|
|
|
|
|
|
// group.
|
|
|
|
|
|
return (candidate_number_group_index >= 0 && |
|
|
|
|
|
HasSuffixString(candidate_groups.at(candidate_number_group_index), |
|
|
|
|
|
formatted_number_groups.at(0))); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} // namespace phonenumbers
|
|
|
} // namespace phonenumbers
|
|
|
} // namespace i18n
|
|
|
} // namespace i18n
|