Skip to content

Commit c76dcb2

Browse files
committed
Improve handling phone context in RFC3966 format.
1 parent e772042 commit c76dcb2

File tree

2 files changed

+109
-35
lines changed

2 files changed

+109
-35
lines changed

java/libphonenumber/src/com/google/i18n/phonenumbers/PhoneNumberUtil.java

Lines changed: 78 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3203,49 +3203,93 @@ private void parseHelper(CharSequence numberToParse, String defaultRegion,
32033203
}
32043204

32053205
/**
3206-
* Converts numberToParse to a form that we can parse and write it to nationalNumber if it is
3207-
* written in RFC3966; otherwise extract a possible number out of it and write to nationalNumber.
3206+
* Converts numberToParse to a form that we can parse and write it to outputNumber if it is
3207+
* written in RFC3966; otherwise extract a possible number out of it and write to outputNumber.
32083208
*/
3209-
private void buildNationalNumberForParsing(String numberToParse, StringBuilder nationalNumber)
3209+
// @VisibleForTesting
3210+
void buildNationalNumberForParsing(String numberToParse, StringBuilder outputNumber)
32103211
throws NumberParseException {
3211-
int indexOfPhoneContext = numberToParse.indexOf(Constants.RFC3966_PHONE_CONTEXT);
3212-
32133212
PhoneContext phoneContext = phoneContextParser.parse(numberToParse);
32143213

3215-
if (phoneContext != null) {
3216-
// If the phone context contains a phone number prefix, we need to capture it, whereas domains
3217-
// will be ignored.
3218-
if (phoneContext.getRawContext().charAt(0) == Constants.PLUS_SIGN) {
3219-
// Additional parameters might follow the phone context. If so, we will remove them here
3220-
// because the parameters after phone context are not important for parsing the phone
3221-
// number.
3222-
nationalNumber.append(phoneContext.getRawContext());
3223-
}
3224-
3225-
// Now append everything between the "tel:" prefix and the phone-context. This should include
3226-
// the national number, an optional extension or isdn-subaddress component. Note we also
3227-
// handle the case when "tel:" is missing, as we have seen in some of the phone number inputs.
3228-
// In that case, we append everything from the beginning.
3229-
int indexOfRfc3966Prefix = numberToParse.indexOf(RFC3966_PREFIX);
3230-
int indexOfNationalNumber =
3231-
(indexOfRfc3966Prefix >= 0) ? indexOfRfc3966Prefix + RFC3966_PREFIX.length() : 0;
3232-
nationalNumber.append(numberToParse.substring(indexOfNationalNumber, indexOfPhoneContext));
3233-
} else {
3214+
if (phoneContext == null) {
32343215
// Extract a possible number from the string passed in (this strips leading characters that
32353216
// could not be the start of a phone number.)
3236-
nationalNumber.append(extractPossibleNumber(numberToParse));
3217+
outputNumber.append(extractPossibleNumber(numberToParse));
3218+
3219+
// Strip the isdn parameter if present
3220+
int idsnPrefixStart = outputNumber.indexOf(RFC3966_ISDN_SUBADDRESS);
3221+
if (idsnPrefixStart > 0) {
3222+
outputNumber.delete(idsnPrefixStart, outputNumber.length());
3223+
}
3224+
return;
32373225
}
32383226

3239-
// Delete the isdn-subaddress and everything after it if it is present. Note extension won't
3240-
// appear at the same time with isdn-subaddress according to paragraph 5.3 of the RFC3966 spec,
3241-
int indexOfIsdn = nationalNumber.indexOf(RFC3966_ISDN_SUBADDRESS);
3242-
if (indexOfIsdn > 0) {
3243-
nationalNumber.delete(indexOfIsdn, nationalNumber.length());
3227+
// Note we also handle the case when "tel:" is missing. In that case, we consider the start of
3228+
// the string to be the start of the number.
3229+
int rfc3966PrefixStart = numberToParse.indexOf(RFC3966_PREFIX);
3230+
int numberStart =
3231+
(rfc3966PrefixStart >= 0) ? rfc3966PrefixStart + RFC3966_PREFIX.length() : 0;
3232+
int numberEnd = numberToParse.indexOf(";", numberStart);
3233+
3234+
if (numberEnd < 0) {
3235+
// If there is no semicolon, we assume the rest of the string is the national number.
3236+
// This should not happen, since we expect to find a semicolon if there is a phone-context.
3237+
numberEnd = numberToParse.length();
32443238
}
3245-
// If both phone context and isdn-subaddress are absent but other parameters are present, the
3246-
// parameters are left in nationalNumber. This is because we are concerned about deleting
3247-
// content from a potential number string when there is no strong evidence that the number is
3248-
// actually written in RFC3966.
3239+
3240+
String numberPart = numberToParse.substring(numberStart, numberEnd);
3241+
3242+
outputNumber.append(constructE164(phoneContext, numberPart));
3243+
3244+
// Append the extension if present.
3245+
int extnPrefixStart = numberToParse.indexOf(RFC3966_EXTN_PREFIX);
3246+
if (extnPrefixStart >= 0) {
3247+
int extnStart = extnPrefixStart + RFC3966_EXTN_PREFIX.length();
3248+
int extnEnd = numberToParse.indexOf(";", extnStart);
3249+
if (extnEnd < 0) {
3250+
extnEnd = numberToParse.length();
3251+
}
3252+
outputNumber.append(DEFAULT_EXTN_PREFIX).append(numberToParse, extnStart, extnEnd);
3253+
}
3254+
}
3255+
3256+
/**
3257+
* Attempts to construct an E164 number from the parsed phone context and numberPart and returns
3258+
* it.
3259+
*
3260+
* If the phone context is a country code, the national prefix is stripped from the numberPart and
3261+
* the E164 number is constructed from the country code and the stripped number.
3262+
*
3263+
* If the phone context is more than just a country code, we fall back to concatenating the whole
3264+
* context with the numberPart.
3265+
*/
3266+
private String constructE164(PhoneContext phoneContext, String numberPart) {
3267+
if (phoneContext.getRawContext().charAt(0) != Constants.PLUS_SIGN) {
3268+
return numberPart;
3269+
}
3270+
3271+
if (phoneContext.getCountryCode() == null) {
3272+
// Fall back to prefixing the national number with the country calling code if the context
3273+
// is more than just a country calling code.
3274+
return phoneContext.getRawContext() + numberPart;
3275+
}
3276+
3277+
// Get the region code and metadata from the phone context country code.
3278+
String regionCode = getRegionCodeForCountryCode(phoneContext.getCountryCode());
3279+
PhoneMetadata regionMetadata = getMetadataForRegionOrCallingCode(phoneContext.getCountryCode(), regionCode);
3280+
3281+
if (regionMetadata == null) {
3282+
// Fall back to prefixing the national number with the country calling code if the country
3283+
// code is invalid.
3284+
return phoneContext.getRawContext() + numberPart;
3285+
}
3286+
3287+
StringBuilder numberWithoutNationalPrefix = new StringBuilder(numberPart);
3288+
maybeStripNationalPrefixAndCarrierCode(numberWithoutNationalPrefix, regionMetadata,
3289+
new StringBuilder());
3290+
3291+
return Constants.PLUS_SIGN + phoneContext.getCountryCode().toString()
3292+
+ numberWithoutNationalPrefix;
32493293
}
32503294

32513295
/**

java/libphonenumber/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@
1616

1717
package com.google.i18n.phonenumbers;
1818

19+
import static org.junit.Assert.assertEquals;
20+
import static org.junit.Assert.assertFalse;
21+
import static org.junit.Assert.assertNotNull;
22+
import static org.junit.Assert.assertNull;
1923
import static org.junit.Assert.assertThrows;
20-
24+
import static org.junit.Assert.assertTrue;
25+
import static org.junit.Assert.fail;
2126
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
2227
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberType;
2328
import com.google.i18n.phonenumbers.PhoneNumberUtil.ValidationResult;
@@ -3279,4 +3284,29 @@ public void run() {
32793284
}
32803285
});
32813286
}
3287+
3288+
public void testBuildNationalNumberForParsing() throws Exception {
3289+
// Test that the national prefix is stripped from the numberPart when the phone context is a
3290+
// country code.
3291+
StringBuilder nationalNumber = new StringBuilder();
3292+
phoneUtil.buildNationalNumberForParsing("tel:033316005;phone-context=+64", nationalNumber);
3293+
assertEquals("+6433316005", nationalNumber.toString());
3294+
nationalNumber.setLength(0);
3295+
// Test that the phone context is ignored if it is not a country code.
3296+
phoneUtil.buildNationalNumberForParsing("tel:033316005;phone-context=abc.nz", nationalNumber);
3297+
assertEquals("033316005", nationalNumber.toString());
3298+
// Test that extensions are correctly parsed.
3299+
nationalNumber.setLength(0);
3300+
phoneUtil.buildNationalNumberForParsing("tel:033316005;ext=1234;phone-context=+64",
3301+
nationalNumber);
3302+
assertEquals("+6433316005 ext. 1234", nationalNumber.toString());
3303+
nationalNumber.setLength(0);
3304+
phoneUtil.buildNationalNumberForParsing("tel:033316005;phone-context=+64;ext=1234",
3305+
nationalNumber);
3306+
assertEquals("+6433316005 ext. 1234", nationalNumber.toString());
3307+
// Test that the isub parameter is removed.
3308+
nationalNumber.setLength(0);
3309+
phoneUtil.buildNationalNumberForParsing("tel:033316005;isub=1234", nationalNumber);
3310+
assertEquals("033316005", nationalNumber.toString());
3311+
}
32823312
}

0 commit comments

Comments
 (0)