Browse Source

* Updating parse documentation to be more clear about what it accepts

* New useful getExampleNumber methods (for testing) - one for invalid
  numbers and one for getting a number of a particular type without
  specifying a country.
pull/1050/head
Lara Scheidegger 10 years ago
parent
commit
8a33f94a22
7 changed files with 317 additions and 22 deletions
  1. +93
    -1
      cpp/src/phonenumbers/phonenumberutil.cc
  2. +37
    -4
      cpp/src/phonenumbers/phonenumberutil.h
  3. +45
    -1
      cpp/test/phonenumbers/phonenumberutil_test.cc
  4. +104
    -8
      java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java
  5. +18
    -1
      java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java
  6. +3
    -0
      java/pending_code_changes.txt
  7. +17
    -7
      javascript/i18n/phonenumbers/phonenumberutil.js

+ 93
- 1
cpp/src/phonenumbers/phonenumberutil.cc View File

@ -1645,6 +1645,59 @@ bool PhoneNumberUtil::GetExampleNumber(const string& region_code,
return GetExampleNumberForType(region_code, FIXED_LINE, number);
}
bool PhoneNumberUtil::GetInvalidExampleNumber(const string& region_code,
PhoneNumber* number) const {
DCHECK(number);
if (!IsValidRegionCode(region_code)) {
LOG(WARNING) << "Invalid or unknown region code (" << region_code
<< ") provided.";
return false;
}
// We start off with a valid fixed-line number since every country supports
// this. Alternatively we could start with a different number type, since
// fixed-line numbers typically have a wide breadth of valid number lengths
// and we may have to make it very short before we get an invalid number.
const PhoneMetadata* region_metadata = GetMetadataForRegion(region_code);
const PhoneNumberDesc* desc =
GetNumberDescByType(*region_metadata, FIXED_LINE);
if (!desc->has_example_number()) {
// This shouldn't happen - we have a test for this.
return false;
}
const string& example_number = desc->example_number();
// Try and make the number invalid. We do this by changing the length. We try
// reducing the length of the number, since currently no region has a number
// that is the same length as kMinLengthForNsn. This is probably quicker than
// making the number longer, which is another alternative. We could also use
// the possible number pattern to extract the possible lengths of the number
// to make this faster, but this method is only for unit-testing so simplicity
// is preferred to performance.
// We don't want to return a number that can't be parsed, so we check the
// number is long enough. We try all possible lengths because phone number
// plans often have overlapping prefixes so the number 123456 might be valid
// as a fixed-line number, and 12345 as a mobile number. It would be faster to
// loop in a different order, but we prefer numbers that look closer to real
// numbers (and it gives us a variety of different lengths for the resulting
// phone numbers - otherwise they would all be kMinLengthForNsn digits long.)
for (size_t phone_number_length = example_number.length() - 1;
phone_number_length >= kMinLengthForNsn;
phone_number_length--) {
string number_to_try = example_number.substr(0, phone_number_length);
PhoneNumber possibly_valid_number;
Parse(number_to_try, region_code, &possibly_valid_number);
// We don't check the return value since we have already checked the
// length, we know example numbers have only valid digits, and we know the
// region code is fine.
if (!IsValidNumber(possibly_valid_number)) {
number->MergeFrom(possibly_valid_number);
return true;
}
}
// We have a test to check that this doesn't happen for any of our supported
// regions.
return false;
}
// Gets a valid number for the specified region_code and type. Returns false if
// the country was unknown or 001 (representing non-geographical regions), or if
// no number exists.
@ -1672,6 +1725,45 @@ bool PhoneNumberUtil::GetExampleNumberForType(
return false;
}
bool PhoneNumberUtil::GetExampleNumberForType(
PhoneNumberUtil::PhoneNumberType type,
PhoneNumber* number) const {
DCHECK(number);
set<string> regions;
GetSupportedRegions(&regions);
for (set<string>::const_iterator it = regions.begin();
it != regions.end(); ++it) {
if (GetExampleNumberForType(*it, type, number)) {
return true;
}
}
// If there wasn't an example number for a region, try the non-geographical
// entities.
set<int> global_network_calling_codes;
GetSupportedGlobalNetworkCallingCodes(&global_network_calling_codes);
for (set<int>::const_iterator it = global_network_calling_codes.begin();
it != global_network_calling_codes.end(); ++it) {
int country_calling_code = *it;
const PhoneMetadata* metadata =
GetMetadataForNonGeographicalRegion(country_calling_code);
const PhoneNumberDesc& desc = metadata->general_desc();
if (desc.has_example_number()) {
ErrorType success = Parse(StrCat(kPlusSign,
country_calling_code,
desc.example_number()),
RegionCode::GetUnknown(), number);
if (success == NO_PARSING_ERROR) {
return true;
} else {
LOG(ERROR) << "Error parsing example number ("
<< static_cast<int>(success) << ")";
}
}
}
// There are no example numbers of this type for any country in the library.
return false;
}
bool PhoneNumberUtil::GetExampleNumberForNonGeoEntity(
int country_calling_code, PhoneNumber* number) const {
DCHECK(number);
@ -1683,7 +1775,7 @@ bool PhoneNumberUtil::GetExampleNumberForNonGeoEntity(
ErrorType success = Parse(StrCat(kPlusSign,
SimpleItoa(country_calling_code),
desc.example_number()),
RegionCode::ZZ(), number);
RegionCode::GetUnknown(), number);
if (success == NO_PARSING_ERROR) {
return true;
} else {


+ 37
- 4
cpp/src/phonenumbers/phonenumberutil.h View File

@ -496,6 +496,18 @@ class PhoneNumberUtil : public Singleton<PhoneNumberUtil> {
bool GetExampleNumber(const string& region_code,
PhoneNumber* number) const;
// Gets an invalid number for the specified region. This is useful for
// unit-testing purposes, where you want to test that will happen with an
// invalid number. Note that the number that is returned will always be able
// to be parsed and will have the correct country code. It may also be a valid
// *short* number/code for this region. Validity checking such
// numbers is handled with ShortNumberInfo.
//
// Returns false when an unsupported region or the region 001 (Earth) is
// passed in.
bool GetInvalidExampleNumber(const string& region_code,
PhoneNumber* number) const;
// Gets a valid number of the specified type for the specified region.
// Returns false if the region was unknown or 001, or if no example number of
// that type could be found. For 001 (representing non-geographical numbers),
@ -504,6 +516,13 @@ class PhoneNumberUtil : public Singleton<PhoneNumberUtil> {
PhoneNumberType type,
PhoneNumber* number) const;
// Gets a valid number for the specified type (it may belong to any country).
// Returns false when the metadata does not contain such information. This
// should only happen when no numbers of this type are allocated anywhere in
// the world anymore.
bool GetExampleNumberForType(PhoneNumberType type,
PhoneNumber* number) const;
// Gets a valid number for the specified country calling code for a
// non-geographical entity. Returns false if the metadata does not contain
// such information, or the country calling code passed in does not belong to
@ -511,10 +530,19 @@ class PhoneNumberUtil : public Singleton<PhoneNumberUtil> {
bool GetExampleNumberForNonGeoEntity(
int country_calling_code, PhoneNumber* number) const;
// Parses a string and returns it in proto buffer format. This method will
// return an error like INVALID_COUNTRY_CODE if the number is not considered
// to be a possible number, and NO_PARSING_ERROR if it parsed correctly. Note
// that validation of whether the number is actually a valid number for a
// Parses a string and returns it as a phone number in proto buffer format.
// The method is quite lenient and looks for a number in the input text
// (raw input) and does not check whether the string is definitely only a
// phone number. To do this, it ignores punctuation and white-space, as well
// as any text before the number (e.g. a leading Tel: ) and trims the
// non-number bits. It will accept a number in any format (E164, national,
// international etc), assuming it can be interpreted with the defaultRegion
// supplied. It also attempts to convert any alpha characters into digits
// if it thinks this is a vanity number of the type "1800 MICROSOFT".
//
// This method will return an error if the number is not considered to be a
// possible number, and NO_PARSING_ERROR if it parsed correctly.
// Note that validation of whether the number is actually a valid number for a
// particular region is not performed. This can be done separately with
// IsValidNumber().
//
@ -526,6 +554,11 @@ class PhoneNumberUtil : public Singleton<PhoneNumberUtil> {
// stored as that of the default country supplied. If the number is guaranteed
// to start with a '+' followed by the country calling code, then
// "ZZ" can be supplied.
//
// Returns an error if the string is not considered to be a viable phone
// number (e.g.too few or too many digits) or if no default region was
// supplied and the number is not in international format (does not
// start with +).
ErrorType Parse(const string& number_to_parse,
const string& default_region,
PhoneNumber* number) const;


+ 45
- 1
cpp/test/phonenumbers/phonenumberutil_test.cc View File

@ -372,6 +372,25 @@ TEST_F(PhoneNumberUtilTest, GetExampleNumber) {
EXPECT_FALSE(phone_util_.GetExampleNumber(RegionCode::UN001(), &test_number));
}
TEST_F(PhoneNumberUtilTest, GetInvalidExampleNumber) {
// RegionCode 001 is reserved for supporting non-geographical country calling
// codes. We don't support getting an invalid example number for it with
// GetInvalidExampleNumber.
PhoneNumber test_number;
EXPECT_FALSE(phone_util_.GetInvalidExampleNumber(RegionCode::UN001(),
&test_number));
EXPECT_EQ(PhoneNumber::default_instance(), test_number);
EXPECT_FALSE(phone_util_.GetInvalidExampleNumber(RegionCode::CS(),
&test_number));
EXPECT_EQ(PhoneNumber::default_instance(), test_number);
EXPECT_TRUE(phone_util_.GetInvalidExampleNumber(RegionCode::US(),
&test_number));
// At least the country calling code should be set correctly.
EXPECT_EQ(1, test_number.country_code());
EXPECT_NE(0, test_number.national_number());
}
TEST_F(PhoneNumberUtilTest, GetExampleNumberForNonGeoEntity) {
PhoneNumber toll_free_number;
toll_free_number.set_country_code(800);
@ -390,6 +409,30 @@ TEST_F(PhoneNumberUtilTest, GetExampleNumberForNonGeoEntity) {
EXPECT_EQ(universal_premium_rate, test_number);
}
TEST_F(PhoneNumberUtilTest, GetExampleNumberWithoutRegion) {
// In our test metadata we don't cover all types: in our real metadata, we do.
PhoneNumber test_number;
bool success = phone_util_.GetExampleNumberForType(
PhoneNumberUtil::FIXED_LINE,
&test_number);
// We test that the call to get an example number succeeded, and that the
// number passed in was modified.
EXPECT_TRUE(success);
EXPECT_NE(PhoneNumber::default_instance(), test_number);
test_number.Clear();
success = phone_util_.GetExampleNumberForType(PhoneNumberUtil::MOBILE,
&test_number);
EXPECT_TRUE(success);
EXPECT_NE(PhoneNumber::default_instance(), test_number);
test_number.Clear();
success = phone_util_.GetExampleNumberForType(PhoneNumberUtil::PREMIUM_RATE,
&test_number);
EXPECT_TRUE(success);
EXPECT_NE(PhoneNumber::default_instance(), test_number);
}
TEST_F(PhoneNumberUtilTest, FormatUSNumber) {
PhoneNumber test_number;
string formatted_number;
@ -2363,7 +2406,7 @@ TEST_F(PhoneNumberUtilTest, ConvertAlphaCharactersInNumber) {
// Try with some non-ASCII characters.
input.assign("1\xE3\x80\x80\xEF\xBC\x88" "800) ABC-DEF"
/* "1 (800) ABCD-DEF" */);
/* "1 (800) ABC-DEF" */);
static const string kExpectedFullwidthOutput =
"1\xE3\x80\x80\xEF\xBC\x88" "800) 222-333" /* "1 (800) 222-333" */;
phone_util_.ConvertAlphaCharactersInNumber(&input);
@ -3184,6 +3227,7 @@ TEST_F(PhoneNumberUtilTest, ParseWithInternationalPrefixes) {
phone_util_.Parse("\xEF\xBC\x8B" "1 (650) 333-6000",
/* "+1 (650) 333-6000" */
RegionCode::SG(), &test_number));
EXPECT_EQ(us_number, test_number);
// Using a soft hyphen U+00AD.
EXPECT_EQ(PhoneNumberUtil::NO_PARSING_ERROR,
phone_util_.Parse("1 (650) 333" "\xC2\xAD" "-6000",


+ 104
- 8
java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java View File

@ -1818,6 +1818,62 @@ public class PhoneNumberUtil {
return getExampleNumberForType(regionCode, PhoneNumberType.FIXED_LINE);
}
/**
* Gets an invalid number for the specified region. This is useful for unit-testing purposes,
* where you want to test what will happen with an invalid number. Note that the number that is
* returned will always be able to be parsed and will have the correct country code. It may also
* be a valid *short* number/code for this region. Validity checking such numbers is handled with
* {@link com.google.i18n.phonenumbers.ShortNumberInfo}.
*
* @param regionCode the region for which an example number is needed
* @return an invalid number for the specified region. Returns null when an unsupported region or
* the region 001 (Earth) is passed in.
*/
public PhoneNumber getInvalidExampleNumber(String regionCode) {
if (!isValidRegionCode(regionCode)) {
logger.log(Level.WARNING, "Invalid or unknown region code provided: " + regionCode);
return null;
}
// We start off with a valid fixed-line number since every country supports this. Alternatively
// we could start with a different number type, since fixed-line numbers typically have a wide
// breadth of valid number lengths and we may have to make it very short before we get an
// invalid number.
PhoneNumberDesc desc = getNumberDescByType(getMetadataForRegion(regionCode),
PhoneNumberType.FIXED_LINE);
if (desc.exampleNumber.equals("")) {
// This shouldn't happen; we have a test for this.
return null;
}
String exampleNumber = desc.exampleNumber;
// Try and make the number invalid. We do this by changing the length. We try reducing the
// length of the number, since currently no region has a number that is the same length as
// MIN_LENGTH_FOR_NSN. This is probably quicker than making the number longer, which is another
// alternative. We could also use the possible number pattern to extract the possible lengths of
// the number to make this faster, but this method is only for unit-testing so simplicity is
// preferred to performance. We don't want to return a number that can't be parsed, so we check
// the number is long enough. We try all possible lengths because phone number plans often have
// overlapping prefixes so the number 123456 might be valid as a fixed-line number, and 12345 as
// a mobile number. It would be faster to loop in a different order, but we prefer numbers that
// look closer to real numbers (and it gives us a variety of different lengths for the resulting
// phone numbers - otherwise they would all be MIN_LENGTH_FOR_NSN digits long.)
for (int phoneNumberLength = exampleNumber.length() - 1;
phoneNumberLength >= MIN_LENGTH_FOR_NSN;
phoneNumberLength--) {
String numberToTry = exampleNumber.substring(0, phoneNumberLength);
try {
PhoneNumber possiblyValidNumber = parse(numberToTry, regionCode);
if (!isValidNumber(possiblyValidNumber)) {
return possiblyValidNumber;
}
} catch (NumberParseException e) {
// Shouldn't happen: we have already checked the length, we know example numbers have
// only valid digits, and we know the region code is fine.
}
}
// We have a test to check that this doesn't happen for any of our supported regions.
return null;
}
/**
* Gets a valid number for the specified region and number type.
*
@ -1845,6 +1901,37 @@ public class PhoneNumberUtil {
return null;
}
/**
* Gets a valid number for the specified number type (it may belong to any country).
*
* @param type the type of number that is needed
* @return a valid number for the specified type. Returns null when the metadata
* does not contain such information. This should only happen when no numbers of this type are
* allocated anywhere in the world anymore.
*/
public PhoneNumber getExampleNumberForType(PhoneNumberType type) {
for (String regionCode : getSupportedRegions()) {
PhoneNumber exampleNumber = getExampleNumberForType(regionCode, type);
if (exampleNumber != null) {
return exampleNumber;
}
}
// If there wasn't an example number for a region, try the non-geographical entities.
for (int countryCallingCode : getSupportedGlobalNetworkCallingCodes()) {
PhoneNumberDesc desc = getNumberDescByType(
getMetadataForNonGeographicalRegion(countryCallingCode), type);
try {
if (!desc.exampleNumber.equals("")) {
return parse("+" + countryCallingCode + desc.exampleNumber, UNKNOWN_REGION);
}
} catch (NumberParseException e) {
logger.log(Level.SEVERE, e.toString());
}
}
// There are no example numbers of this type for any country in the library.
return null;
}
/**
* Gets a valid number for the specified country calling code for a non-geographical entity.
*
@ -1859,7 +1946,7 @@ public class PhoneNumberUtil {
PhoneNumberDesc desc = metadata.generalDesc;
try {
if (!desc.exampleNumber.equals("")) {
return parse("+" + countryCallingCode + desc.exampleNumber, "ZZ");
return parse("+" + countryCallingCode + desc.exampleNumber, UNKNOWN_REGION);
}
} catch (NumberParseException e) {
logger.log(Level.SEVERE, e.toString());
@ -2670,10 +2757,18 @@ public class PhoneNumberUtil {
}
/**
* Parses a string and returns it in proto buffer format. This method will throw a
* {@link com.google.i18n.phonenumbers.NumberParseException} if the number is not considered to be
* a possible number. Note that validation of whether the number is actually a valid number for a
* particular region is not performed. This can be done separately with {@link #isValidNumber}.
* Parses a string and returns it as a phone number in proto buffer format. The method is quite
* lenient and looks for a number in the input text (raw input) and does not check whether the
* string is definitely only a phone number. To do this, it ignores punctuation and white-space,
* as well as any text before the number (e.g. a leading Tel: ) and trims the non-number bits.
* It will accept a number in any format (E164, national, international etc), assuming it can be
* interpreted with the defaultRegion supplied. It also attempts to convert any alpha characters
* into digits if it thinks this is a vanity number of the type "1800 MICROSOFT".
*
* <p> This method will throw a {@link com.google.i18n.phonenumbers.NumberParseException} if the
* number is not considered to be a possible number. Note that validation of whether the number
* is actually a valid number for a particular region is not performed. This can be done
* separately with {@link #isValidNumber}.
*
* @param numberToParse number that we are attempting to parse. This can contain formatting
* such as +, ( and -, as well as a phone number extension. It can also
@ -2685,9 +2780,10 @@ public class PhoneNumberUtil {
* start with a '+' followed by the country calling code, then
* "ZZ" or null can be supplied.
* @return a phone number proto buffer filled with the parsed number
* @throws NumberParseException if the string is not considered to be a viable phone number or if
* no default region was supplied and the number is not in
* international format (does not start with +)
* @throws NumberParseException if the string is not considered to be a viable phone number (e.g.
* too few or too many digits) or if no default region was supplied
* and the number is not in international format (does not start
* with +)
*/
public PhoneNumber parse(String numberToParse, String defaultRegion)
throws NumberParseException {


+ 18
- 1
java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java View File

@ -330,7 +330,17 @@ public class PhoneNumberUtilTest extends TestMetadataTestCase {
PhoneNumberUtil.PhoneNumberType.MOBILE));
// RegionCode 001 is reserved for supporting non-geographical country calling code. We don't
// support getting an example number for it with this method.
assertEquals(null, phoneUtil.getExampleNumber(RegionCode.UN001));
assertNull(phoneUtil.getExampleNumber(RegionCode.UN001));
}
public void testGetInvalidExampleNumber() {
// RegionCode 001 is reserved for supporting non-geographical country calling codes. We don't
// support getting an invalid example number for it with getInvalidExampleNumber.
assertNull(phoneUtil.getInvalidExampleNumber(RegionCode.UN001));
assertNull(phoneUtil.getInvalidExampleNumber(RegionCode.CS));
PhoneNumber usInvalidNumber = phoneUtil.getInvalidExampleNumber(RegionCode.US);
assertEquals(1, usInvalidNumber.getCountryCode());
assertFalse(usInvalidNumber.getNationalNumber() == 0);
}
public void testGetExampleNumberForNonGeoEntity() {
@ -338,6 +348,13 @@ public class PhoneNumberUtilTest extends TestMetadataTestCase {
assertEquals(UNIVERSAL_PREMIUM_RATE, phoneUtil.getExampleNumberForNonGeoEntity(979));
}
public void testGetExampleNumberWithoutRegion() {
// In our test metadata we don't cover all types: in our real metadata, we do.
assertNotNull(phoneUtil.getExampleNumberForType(PhoneNumberUtil.PhoneNumberType.FIXED_LINE));
assertNotNull(phoneUtil.getExampleNumberForType(PhoneNumberUtil.PhoneNumberType.MOBILE));
assertNotNull(phoneUtil.getExampleNumberForType(PhoneNumberUtil.PhoneNumberType.PREMIUM_RATE));
}
public void testConvertAlphaCharactersInNumber() {
String input = "1800-ABC-DEF";
// Alpha chars are converted to digits; everything else is left untouched.


+ 3
- 0
java/pending_code_changes.txt View File

@ -1,3 +1,6 @@
Code changes:
- Added java/pending_code_changes.txt for contributors to track code changes
between releases.
- Added getExampleNumberForType that doesn't take in a region, and
getInvalidExampleNumber
- Improvements to javadoc for parse method

+ 17
- 7
javascript/i18n/phonenumbers/phonenumberutil.js View File

@ -3663,11 +3663,20 @@ i18n.phonenumbers.PhoneNumberUtil.prototype.checkRegionForParsing_ = function(
/**
* Parses a string and returns it in proto buffer format. This method will throw
* a {@link i18n.phonenumbers.Error} if the number is not considered to be a
* possible number. Note that validation of whether the number is actually a
* valid number for a particular region is not performed. This can be done
* separately with {@link #isValidNumber}.
* Parses a string and returns it as a phone number in proto buffer format. The
* method is quite lenient and looks for a number in the input text (raw input)
* and does not check whether the string is definitely only a phone number. To
* do this, it ignores punctuation and white-space, as well as any text before
* the number (e.g. a leading Tel: ) and trims the non-number bits. It will
* accept a number in any format (E164, national, international etc), assuming
* it can be interpreted with the defaultRegion supplied. It also attempts to
* convert any alpha characters into digits if it thinks this is a vanity number
* of the type "1800 MICROSOFT".
*
* This method will throw a {@link i18n.phonenumbers.Error} if the number is not
* considered to be a possible number. Note that validation of whether the
* number is actually a valid number for a particular region is not performed.
* This can be done separately with {@link #isValidNumber}.
*
* @param {?string} numberToParse number that we are attempting to parse. This
* can contain formatting such as +, ( and -, as well as a phone number
@ -3681,8 +3690,9 @@ i18n.phonenumbers.PhoneNumberUtil.prototype.checkRegionForParsing_ = function(
* @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
* with the parsed number.
* @throws {i18n.phonenumbers.Error} if the string is not considered to be a
* viable phone number or if no default region was supplied and the number
* is not in international format (does not start with +).
* viable phone number (e.g. too few or too many digits) or if no default
* region was supplied and the number is not in international format (does
* not start with +).
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parse = function(numberToParse,
defaultRegion) {


Loading…
Cancel
Save