+October 6th, 2011: libphonenumber-4.0
+* Code changes
+ - New function formatNumberForMobileDialing, which attempts to format a number in
+ such a way that the call can be connected from a mobile phone. If this is
+ impossible, for example for numbers that cannot be internationally dialled,
+ then an empty string is returned.
+ - Fallback functionality to English for non-CJK languages for geocoding
+
+* Metadata changes
+ - Collecting data for emergency numbers
+ - Updates: AR, AU, BR, CN, CZ, EG, GD, IE, IL, JM, KW, KH, SD, SO, TR, UG, UZ
+ - Geocoding data changes: AO, AR, AT, BJ, BR, CD, CG, CI, CL, CN, CV, DE, ES,
+ FR, GR, GW, HU, KM, MR, MZ, NL, PL, PT, SE, ST, SZ
+
September 13th, 2011: libphonenumber-3.9
* Code changes
- Enable AsYouTypeFormatter to handle long IDD and NDD.
private final Set<String> nanpaRegions = new HashSet<String>(35);
private static final int NANPA_COUNTRY_CODE = 1;
+ // The prefix that needs to be inserted in front of a Colombian landline number when dialed from
+ // a mobile phone in Colombia.
+ private static final String COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = "3";
+
// The PLUS_SIGN signifies the international prefix.
static final char PLUS_SIGN = '+';
* string if no character used to start phone numbers (such as + or any digit) is
* found in the number
*/
+ // @VisibleForTesting
static String extractPossibleNumber(String number) {
Matcher m = VALID_START_CHAR_PATTERN.matcher(number);
if (m.find()) {
* @param number string to be checked for viability as a phone number
* @return true if the number could be a phone number of some sort, otherwise false
*/
+ // @VisibleForTesting
static boolean isViablePhoneNumber(String number) {
if (number.length() < MIN_LENGTH_FOR_NSN) {
return false;
}
/**
- * Convenience method to enable tests to get a list of what regions the library has metadata for.
+ * Convenience method to get a list of what regions the library has metadata for.
*/
public Set<String> getSupportedRegions() {
return supportedRegions;
}
/**
+ * Returns a number formatted in such a way that it can be dialed from a mobile phone in a
+ * specific region. If the number cannot be reached from the region (e.g. some countries block
+ * toll-free numbers from being called outside of the country), the method returns an empty
+ * string.
+ *
+ * @param number the phone number to be formatted
+ * @param regionCallingFrom the region where the call is being placed
+ * @param withFormatting whether the number should be returned with formatting symbols, such as
+ * spaces and dashes.
+ * @return the formatted phone number
+ */
+ public String formatNumberForMobileDialing(PhoneNumber number, String regionCallingFrom,
+ boolean withFormatting) {
+ String regionCode = getRegionCodeForNumber(number);
+ if (!isValidRegionCode(regionCode)) {
+ return number.hasRawInput() ? number.getRawInput() : "";
+ }
+
+ String formattedNumber;
+ // Clear the extension, as that part cannot normally be dialed together with the main number.
+ number.clearExtension();
+ PhoneNumberType numberType = getNumberType(number);
+ if ((regionCode == "CO") && (regionCallingFrom == "CO") &&
+ (numberType == PhoneNumberType.FIXED_LINE)) {
+ formattedNumber =
+ formatNationalNumberWithCarrierCode(number, COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX);
+ } else if ((regionCode == "BR") && (regionCallingFrom == "BR") &&
+ ((numberType == PhoneNumberType.FIXED_LINE) || (numberType == PhoneNumberType.MOBILE) ||
+ (numberType == PhoneNumberType.FIXED_LINE_OR_MOBILE))) {
+ formattedNumber = number.hasPreferredDomesticCarrierCode()
+ ? formatNationalNumberWithPreferredCarrierCode(number, "")
+ // Brazilian fixed line and mobile numbers need to be dialed with a carrier code when
+ // called within Brazil. Without that, most of the carriers won't connect the call.
+ // Because of that, we return an empty string here.
+ : "";
+ } else if (canBeInternationallyDialled(number)) {
+ return withFormatting ? format(number, PhoneNumberFormat.INTERNATIONAL)
+ : format(number, PhoneNumberFormat.E164);
+ } else {
+ formattedNumber = (regionCallingFrom == regionCode)
+ ? format(number, PhoneNumberFormat.NATIONAL) : "";
+ }
+ return withFormatting ? formattedNumber : normalizeDigitsOnly(formattedNumber);
+ }
+
+ /**
* Formats a phone number for out-of-country dialing purposes. If no regionCallingFrom is
* supplied, we format the number in its INTERNATIONAL format. If the country calling code is the
* same as that of the region where the number is from, then NATIONAL formatting will be applied.
* @return the national significant number of the PhoneNumber object passed in
*/
public String getNationalSignificantNumber(PhoneNumber number) {
- // The leading zero in the national (significant) number of an Italian phone number has a
- // special meaning. Unlike the rest of the world, it indicates the number is a landline
- // number. There have been plans to migrate landline numbers to start with the digit two since
- // December 2000, but it has not yet happened.
- // See http://en.wikipedia.org/wiki/%2B39 for more details.
- // Other regions such as Cote d'Ivoire and Gabon use this for their mobile numbers.
- StringBuilder nationalNumber = new StringBuilder(
- (number.hasItalianLeadingZero() &&
- number.isItalianLeadingZero() &&
- isLeadingZeroPossible(number.getCountryCode()))
- ? "0" : ""
- );
+ // If a leading zero has been set, we prefix this now. Note this is not a national prefix.
+ StringBuilder nationalNumber = new StringBuilder(number.isItalianLeadingZero() ? "0" : "");
nationalNumber.append(number.getNationalNumber());
return nationalNumber.toString();
}
formattedNumber.insert(0, " ").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case RFC3966:
- formattedNumber.insert(0, "-").insert(0, countryCallingCode) .insert(0, PLUS_SIGN);
+ formattedNumber.insert(0, "-").insert(0, countryCallingCode).insert(0, PLUS_SIGN);
return;
case NATIONAL:
default:
* only populated when keepCountryCodeSource is true.
* @return the country calling code extracted or 0 if none could be extracted
*/
+ // @VisibleForTesting
int maybeExtractCountryCode(String number, PhoneMetadata defaultRegionMetadata,
StringBuilder nationalNumber, boolean keepRawInput,
PhoneNumber phoneNumber)
* removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number did
* not seem to be in international format.
*/
+ // @VisibleForTesting
CountryCodeSource maybeStripInternationalPrefixAndNormalize(
StringBuilder number,
String possibleIddPrefix) {
* @param metadata the metadata for the region that we think this number is from
* @return the carrier code extracted if it is present, otherwise return an empty string.
*/
+ // @VisibleForTesting
String maybeStripNationalPrefixAndCarrierCode(StringBuilder number, PhoneMetadata metadata) {
String carrierCode = "";
int numberLength = number.length();
* @param number the non-normalized telephone number that we wish to strip the extension from
* @return the phone extension
*/
+ // @VisibleForTesting
String maybeStripExtension(StringBuilder number) {
Matcher m = EXTN_PATTERN.matcher(number);
// If we find a potential extension, and the number preceding this is a viable number, we assume
return this;
}
+ // required PhoneNumberDesc emergency = 27;
+ private boolean hasEmergency;
+ private PhoneNumberDesc emergency_ = null;
+ public boolean hasEmergency() { return hasEmergency; }
+ public PhoneNumberDesc getEmergency() { return emergency_; }
+ public PhoneMetadata setEmergency(PhoneNumberDesc value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ hasEmergency = true;
+ emergency_ = value;
+ return this;
+ }
+
// required PhoneNumberDesc noInternationalDialling = 24;
private boolean hasNoInternationalDialling;
private PhoneNumberDesc noInternationalDialling_ = null;
if (hasUan) {
uan_.writeExternal(objectOutput);
}
+ objectOutput.writeBoolean(hasEmergency);
+ if (hasEmergency) {
+ emergency_.writeExternal(objectOutput);
+ }
objectOutput.writeBoolean(hasNoInternationalDialling);
if (hasNoInternationalDialling) {
noInternationalDialling_.writeExternal(objectOutput);
if (hasDesc) {
PhoneNumberDesc desc = new PhoneNumberDesc();
desc.readExternal(objectInput);
+ setEmergency(desc);
+ }
+ hasDesc = objectInput.readBoolean();
+ if (hasDesc) {
+ PhoneNumberDesc desc = new PhoneNumberDesc();
+ desc.readExternal(objectInput);
setNoInternationalDialling(desc);
}
}
private AreaCodeMap getPhonePrefixDescriptions(
- int countryCallingCode, String language, String script, String region) {
- String fileName = mappingFileProvider.getFileName(countryCallingCode, language, script, region);
+ int prefixMapKey, String language, String script, String region) {
+ String fileName = mappingFileProvider.getFileName(prefixMapKey, language, script, region);
if (fileName.length() == 0) {
return null;
}
countryCallingCode : (1000 + (int) (number.getNationalNumber() / 10000000));
AreaCodeMap phonePrefixDescriptions =
getPhonePrefixDescriptions(phonePrefix, lang, script, region);
- String description = phonePrefixDescriptions != null
+ String description = (phonePrefixDescriptions != null)
? phonePrefixDescriptions.lookup(number)
- : "";
- return description == null ? "" : description;
+ : null;
+ // When a location is not available in the requested language, fall back to English.
+ if ((description == null || description.length() == 0) && mayFallBackToEnglish(lang)) {
+ AreaCodeMap defaultMap = getPhonePrefixDescriptions(phonePrefix, "en", "", "");
+ if (defaultMap == null) {
+ return "";
+ }
+ description = defaultMap.lookup(number);
+ }
+ return description != null ? description : "";
+ }
+
+ private boolean mayFallBackToEnglish(String lang) {
+ // Don't fall back to English if the requested language is among the following:
+ // - Chinese
+ // - Japanese
+ // - Korean
+ return !lang.equals("zh") && !lang.equals("ja") && !lang.equals("ko");
}
}
phoneUtil.formatNationalNumberWithPreferredCarrierCode(usNumber, "15"));
}
+ public void testFormatNumberForMobileDialing() {
+ // US toll free numbers are marked as noInternationalDialling in the test metadata for testing
+ // purposes.
+ assertEquals("800 253 0000",
+ phoneUtil.formatNumberForMobileDialing(US_TOLLFREE, RegionCode.US, true));
+ assertEquals("", phoneUtil.formatNumberForMobileDialing(US_TOLLFREE, RegionCode.CN, true));
+ assertEquals("+1 650 253 0000",
+ phoneUtil.formatNumberForMobileDialing(US_NUMBER, RegionCode.US, true));
+ PhoneNumber usNumberWithExtn = new PhoneNumber().mergeFrom(US_NUMBER).setExtension("1234");
+ assertEquals("+1 650 253 0000",
+ phoneUtil.formatNumberForMobileDialing(usNumberWithExtn, RegionCode.US, true));
+
+ assertEquals("8002530000",
+ phoneUtil.formatNumberForMobileDialing(US_TOLLFREE, RegionCode.US, false));
+ assertEquals("", phoneUtil.formatNumberForMobileDialing(US_TOLLFREE, RegionCode.CN, false));
+ assertEquals("+16502530000",
+ phoneUtil.formatNumberForMobileDialing(US_NUMBER, RegionCode.US, false));
+ assertEquals("+16502530000",
+ phoneUtil.formatNumberForMobileDialing(usNumberWithExtn, RegionCode.US, false));
+ }
+
public void testFormatByPattern() {
NumberFormat newNumFormat = new NumberFormat();
newNumFormat.setPattern("(\\d{3})(\\d{3})(\\d{4})");
// a result, the country name of United States in simplified Chinese is returned.
assertEquals("\u7F8E\u56FD",
geocoder.getDescriptionForNumber(US_NUMBER1, Locale.SIMPLIFIED_CHINESE));
- assertEquals("Stati Uniti",
- geocoder.getDescriptionForNumber(US_NUMBER1, Locale.ITALIAN));
assertEquals("Bahamas",
geocoder.getDescriptionForNumber(BS_NUMBER1, new Locale("en", "US")));
assertEquals("Australia",
geocoder.getDescriptionForNumber(KO_NUMBER1, Locale.KOREAN));
assertEquals("\uC778\uCC9C",
geocoder.getDescriptionForNumber(KO_NUMBER2, Locale.KOREAN));
- assertEquals("\uC81C\uC8FC",
+ }
+
+ public void testGetDescriptionForFallBack() {
+ // No fallback, as the location name for the given phone number is available in the requested
+ // language.
+ assertEquals("Kalifornien",
+ geocoder.getDescriptionForNumber(US_NUMBER1, Locale.GERMAN));
+ // German falls back to English.
+ assertEquals("New York, NY",
+ geocoder.getDescriptionForNumber(US_NUMBER3, Locale.GERMAN));
+ // Italian falls back to English.
+ assertEquals("CA",
+ geocoder.getDescriptionForNumber(US_NUMBER1, Locale.ITALIAN));
+ // Korean doesn't fall back to English.
+ assertEquals("\uB300\uD55C\uBBFC\uAD6D",
geocoder.getDescriptionForNumber(KO_NUMBER3, Locale.KOREAN));
}
<!ELEMENT territories (territory+)>
<!ELEMENT territory (availableFormats?, generalDesc?, noInternationalDialling?,
areaCodeOptional?, fixedLine?, mobile?, pager?, tollFree?, premiumRate?,
- sharedCost?, personalNumber?, voip?, uan?, shortCode?)>
+ sharedCost?, personalNumber?, voip?, uan?, shortCode?, emergency?)>
<!ELEMENT generalDesc (nationalNumberPattern, possibleNumberPattern)>
<!ELEMENT noInternationalDialling (nationalNumberPattern, possibleNumberPattern,
exampleNumber?)>
<!ELEMENT voip (nationalNumberPattern?, possibleNumberPattern?, exampleNumber?)>
<!ELEMENT uan (nationalNumberPattern?, possibleNumberPattern?, exampleNumber?)>
<!ELEMENT shortCode (nationalNumberPattern?, possibleNumberPattern?, exampleNumber?)>
+ <!ELEMENT emergency (nationalNumberPattern?, possibleNumberPattern?, exampleNumber?)>
<!ELEMENT availableFormats (numberFormat+)>
<!ELEMENT nationalNumberPattern (#PCDATA)>
<!ELEMENT possibleNumberPattern (#PCDATA)>
</nationalNumberPattern>
<possibleNumberPattern>\d{6,11}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>810\d{7}</nationalNumberPattern>
+ <possibleNumberPattern>\d{10}</possibleNumberPattern>
+ <exampleNumber>8101234567</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- Also covering fixed satellite service numbers (670). -->
<nationalNumberPattern>
<nationalNumberPattern>[1-578]\d{5,9}</nationalNumberPattern>
<possibleNumberPattern>\d{6,10}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>
+ 1(?:
+ 3(?:
+ \d{4}|
+ 00\d{6}
+ )|
+ 80(?:
+ 0\d{6}|
+ 2\d{3}
+ )
+ )
+ </nationalNumberPattern>
+ <possibleNumberPattern>\d{6,10}</possibleNumberPattern>
+ <exampleNumber>1300123456</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- Excludes prefixes used by Cocos Islands and Christmas Islands -->
<nationalNumberPattern>
<nationalNumberPattern>[1-9]\d{7,9}</nationalNumberPattern>
<possibleNumberPattern>\d{8,10}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>
+ (?:
+ 400\d|
+ 3003
+ )\d{4}
+ </nationalNumberPattern>
+ <possibleNumberPattern>\d{8}</possibleNumberPattern>
+ <exampleNumber>40041234</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- According to this publication, the prefixes 11 53, 11 54 and 11 57 are to be used for
mobile phones prior to the introduction of a ninth digit. It is not clear whether they
3003
)\d{4}
</nationalNumberPattern>
+ <possibleNumberPattern>\d{8}</possibleNumberPattern>
<exampleNumber>40041234</exampleNumber>
</sharedCost>
</territory>
</nationalNumberPattern>
<possibleNumberPattern>\d{4,12}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>
+ (?:
+ 4|
+ (?:
+ 10
+ )?8
+ )00\d{7}
+ </nationalNumberPattern>
+ <possibleNumberPattern>\d{10,12}</possibleNumberPattern>
+ <exampleNumber>4001234567</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- 0432 increased to 8 digits on October 24, 2009. 0791 increased to 8 digits on
August 28, 2011. 0475 is the prefix for Tongliao but is not in the ITU data. -->
)|
1(?:
1[0-35-9]|
- 2|
37|
46|
75|
<possibleNumberPattern>\d{9}</possibleNumberPattern>
</generalDesc>
<fixedLine>
+ <!-- Numbers belonging to private communication networks are included here. They start with
+ 972, 973 and 974, and are reachable by the public. -->
<nationalNumberPattern>
2\d{8}|
(?:
3[1257-9]|
4[16-9]|
5[13-9]
- )\d{7}
+ )\d{7}|
+ 97[234]\d{6}
</nationalNumberPattern>
<exampleNumber>212345678</exampleNumber>
</fixedLine>
</numberFormat>
<numberFormat pattern="(\d{3})(\d{3})(\d{4})">
<leadingDigits>
- 15[0-2]|
+ 1(?:
+ 0[0169]|
+ 1[124]|
+ 2[0278]|
+ 5[0-2]
+ )|
[89]00
</leadingDigits>
<format>$1 $2 $3</format>
<exampleNumber>234567890</exampleNumber>
</fixedLine>
<mobile>
+ <!-- Egypt is switching to a 10-digit pattern on October 6th. This will run in parallel with
+ the old pattern for three months, so we support both here in the meantime. -->
<nationalNumberPattern>
1(?:
[0-246-9]|
5[0-2]
+ )\d{7}|
+ 1(?:
+ 0[0169]|
+ 1[124]|
+ 2[0278]
)\d{7}
</nationalNumberPattern>
<possibleNumberPattern>\d{9,10}</possibleNumberPattern>
- <exampleNumber>101234567</exampleNumber>
+ <exampleNumber>1001234567</exampleNumber>
</mobile>
<tollFree>
<nationalNumberPattern>800\d{7}</nationalNumberPattern>
3[5-9]|
4[0-49]|
5[5-79]|
+ 68|
73|
90
)|
<nationalNumberPattern>[124-9]\d{6,9}</nationalNumberPattern>
<possibleNumberPattern>\d{5,10}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>18[59]0\d{6}</nationalNumberPattern>
+ <possibleNumberPattern>\d{10}</possibleNumberPattern>
+ <exampleNumber>1850123456</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- We allow 6-7 digit subscriber numbers for the 021 area code since that seems to be
reflected by the numbers in the Yellow Pages. The 023, 043, 052 and 064 area codes also
<exampleNumber>501234567</exampleNumber>
</mobile>
<tollFree>
+ <!-- Online 1-809 numbers now classify themselves as "toll-free". -->
<nationalNumberPattern>
1(?:
- 80[01]\d{3}|
+ 80[019]\d{3}|
255
)\d{3}
</nationalNumberPattern>
<exampleNumber>1919123456</exampleNumber>
</premiumRate>
<sharedCost>
- <nationalNumberPattern>
- 1(?:
- 700|
- 809
- )\d{6}
- </nationalNumberPattern>
+ <nationalNumberPattern>1700\d{6}</nationalNumberPattern>
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>1700123456</exampleNumber>
</sharedCost>
<possibleNumberPattern>\d{7}(?:\d{3})?</possibleNumberPattern>
</generalDesc>
<fixedLine>
+ <!-- Numbers have been found online for ranges 62x,63x, 656, 66[2-489]. -->
<nationalNumberPattern>
876(?:
5(?:
)|
6(?:
0[1-3579]|
- 1[027]|
- 2[3-5]|
- 34|
- [45]0|
- 63|
+ 1[027-9]|
+ [23]\d|
+ 40|
+ 5[06]|
+ 6[2-489]|
7[05]|
8[04]|
9[4-9]
- )
+ )|
7(?:
0[2-689]|
[1-6]\d|
</fixedLine>
<mobile>
<!-- Adding 27, 28, 299, 31, 508, 527 and 566 as extra prefixes, as they have been found to
- be valid by sending SMSs and looking at online number lookup sites. -->
+ be valid by sending SMSs and looking at online number lookup sites.
+
+ Numbers have been found online for the following ranges 29x, 53x, 54x, 55x, 56x.
+ Most have mobile numbers online so we have put them under mobile but is possible
+ they are a mixture of fixed line and mobile.
+ -->
<nationalNumberPattern>
876(?:
- 2(?:
- [178]\d|
- 99
- )|
+ 2[1789]\d|
[348]\d{2}|
5(?:
08|
27|
- 66|
- [78]\d
+ 6[0-24-9]|
+ [3-578]\d
)|
7(?:
0[07]|
</fixedLine>
<mobile>
<!-- Adding 86 from numbers found online, along with 88 (these numbers seem to be one digit
- longer as well.) -->
+ longer as well.) Adding 97 followed by 7 digits as such numbers have been found online.
+ -->
<nationalNumberPattern>
(?:
(?:
1[0-35-9]|
6[6-9]|
- 7[06-89]|
- 9\d
+ 7[06-89]
)[1-9]|
8(?:
0[89]|
5[2-689]|
8\d{2}|
[13469]\d|
+ )|
+ 9(?:
+ [0-689][1-9]|
+ 7[1-9]\d?
)
)\d{5}
</nationalNumberPattern>
<nationalNumberPattern>
(?:
5(?:
- 0[0-25]|
+ 0[0-26]|
5\d
)|
6(?:
5[015-9]|
6\d|
7[067]|
- 99
+ 9[69]
)|
9(?:
0[09]|
<exampleNumber>1123456</exampleNumber>
</fixedLine>
<mobile>
- <!-- Adding 76[389] from numbers where SMSs have been received and online examples have been
- found. -->
<nationalNumberPattern>
(?:
3\d|
<possibleNumberPattern>\d{9,11}</possibleNumberPattern>
<exampleNumber>900123456</exampleNumber>
</premiumRate>
+ <emergency>
+ <nationalNumberPattern>111</nationalNumberPattern>
+ <possibleNumberPattern>\d{3}</possibleNumberPattern>
+ <exampleNumber>111</exampleNumber>
+ </emergency>
</territory>
<!-- Oman -->
</fixedLine>
<mobile>
<!-- Adding 90X as online numbers have been found with this prefix. -->
- <nationalNumberPattern>9[01259]\d{7}</nationalNumberPattern>
+ <nationalNumberPattern>9[012569]\d{7}</nationalNumberPattern>
<exampleNumber>911231234</exampleNumber>
</mobile>
</territory>
<!-- Somalia -->
<!-- http://www.itu.int/oth/T02020000C0/en -->
+ <!-- http://en.wikipedia.org/wiki/+252 -->
<!-- This document seems to cover only a small set of prefixes in Somalia.
Somalia has limited information available, and the numerous telecom carriers
were previously working under an unregulated environment. The extra prefixes
<territory id="SO" countryCode="252" internationalPrefix="00">
<availableFormats>
<!-- These follow formats online, such as www.hortel.net/contact_us.html -->
- <numberFormat pattern="([13-5])(\d{6})">
+ <numberFormat pattern="(\d)(\d{6})">
<leadingDigits>[13-5]</leadingDigits>
<format>$1 $2</format>
</numberFormat>
+ <numberFormat pattern="(2)(\d{7})">
+ <leadingDigits>2</leadingDigits>
+ <format>$1 $2</format>
+ </numberFormat>
<!-- Unfortunately numbers beginning with 1 are hard to format based on prefixes, since it
depends on number length. -->
- <numberFormat pattern="([19]\d)(\d{6})">
- <leadingDigits>15|9</leadingDigits>
+ <numberFormat pattern="([169]\d)(\d{6})">
+ <leadingDigits>
+ 15|
+ 62|
+ 9
+ </leadingDigits>
+ <format>$1 $2</format>
+ </numberFormat>
+ <numberFormat pattern="(61)(\d{7})">
+ <leadingDigits>61</leadingDigits>
+ <format>$1 $2</format>
+ </numberFormat>
+ <numberFormat pattern="(699)(\d{6})">
+ <leadingDigits>699</leadingDigits>
<format>$1 $2</format>
</numberFormat>
</availableFormats>
<generalDesc>
- <nationalNumberPattern>[13-59]\d{6,7}</nationalNumberPattern>
- <possibleNumberPattern>\d{7,8}</possibleNumberPattern>
+ <nationalNumberPattern>[1-69]\d{6,8}</nationalNumberPattern>
+ <possibleNumberPattern>\d{7,9}</possibleNumberPattern>
</generalDesc>
<fixedLine>
+ <!-- 699 has been added from prefixes found in online numbers. -->
<nationalNumberPattern>
(?:
5[57-9]|
+ 6[19]\d{2}|
[134]\d
)\d{5}
</nationalNumberPattern>
- <possibleNumberPattern>\d{7}</possibleNumberPattern>
+ <possibleNumberPattern>\d{7,9}</possibleNumberPattern>
<!-- Example numbers are test numbers from the document. -->
<exampleNumber>5522010</exampleNumber>
</fixedLine>
<mobile>
+ <!-- 24 is used by the carrier Telesom. -->
+ <!-- 62 has been added from prefixes found in online numbers. -->
<nationalNumberPattern>
(?:
- 9[01]|
- 15
+ 15|
+ 24|
+ 62|
+ 9[01]
)\d{6}
</nationalNumberPattern>
<possibleNumberPattern>\d{8}</possibleNumberPattern>
</nationalNumberPattern>
<possibleNumberPattern>\d{7,10}</possibleNumberPattern>
</generalDesc>
+ <noInternationalDialling>
+ <nationalNumberPattern>444\d{4}</nationalNumberPattern>
+ <possibleNumberPattern>\d{7}</possibleNumberPattern>
+ <exampleNumber>4441444</exampleNumber>
+ </noInternationalDialling>
<fixedLine>
<!-- Includes numbers starting with 392 for Northern Cyprus. -->
<nationalNumberPattern>
<territory id="UG" countryCode="256" internationalPrefix="00[057]"
nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG">
<availableFormats>
- <numberFormat pattern="([247-9]\d{2})(\d{6})">
+ <numberFormat pattern="(\d{3})(\d{6})">
<leadingDigits>
[7-9]|
200|
</leadingDigits>
<format>$1 $2</format>
</numberFormat>
- <numberFormat pattern="([34]\d)(\d{7})">
+ <numberFormat pattern="(\d{2})(\d{7})">
<leadingDigits>
+ 204|
3|
4(?:
[1-5]|
96
)\d{6}|
20(?:
- 0\d|
+ [04]\d|
24
)\d{5}
</nationalNumberPattern>
<exampleNumber>312345678</exampleNumber>
</fixedLine>
<mobile>
- <!-- Added 79X and 70[5-7] from online numbers found with this prefix. -->
+ <!-- Added 79[5-9] and 70[5-7] from online numbers found with this prefix. -->
<nationalNumberPattern>
7(?:
[15789]\d|
<possibleNumberPattern>\d{10}</possibleNumberPattern>
<exampleNumber>5002345678</exampleNumber>
</personalNumber>
+ <emergency>
+ <nationalNumberPattern>911</nationalNumberPattern>
+ <possibleNumberPattern>\d{3}</possibleNumberPattern>
+ <exampleNumber>911</exampleNumber>
+ </emergency>
</territory>
<!-- Uruguay -->
</territory>
<!-- Uzbekistan -->
+ <!-- http://www.ttts.uz/eng/telephone_codes/codes_uzb_eng -->
<!-- http://www.itu.int/oth/T02020000E1/en -->
<territory id="UZ" countryCode="998" internationalPrefix="8~10"
nationalPrefix="8" nationalPrefixFormattingRule="$NP$FG">
<possibleNumberPattern>\d{7,9}</possibleNumberPattern>
</generalDesc>
<fixedLine>
- <!-- Adding 70 prefix as suggested by http://www.ttts.uz/eng/telephone_codes/codes_uzb_eng
- -->
<nationalNumberPattern>
(?:
- 6[125679]|
- 7[0-69]
- )\d{7}
+ 6(?:
+ 1(?:
+ 22|
+ 3[124]|
+ 4[1-4]|
+ 5[123578]|
+ 64
+ )|
+ 2(?:
+ 22|
+ 3[0-57-9]|
+ 41
+ )|
+ 5(?:
+ 22|
+ 3[3-7]|
+ 5[024-8]
+ )|
+ 6\d{2}|
+ 7(?:
+ [23]\d|
+ 7[69]|
+ )|
+ 9(?:
+ 4[1-8]|
+ 6[135]
+ )
+ )|
+ 7(?:
+ 0(?:
+ 5[4-9]|
+ 6[0146]|
+ 7[12456]|
+ 9[135-8]
+ )|
+ 12\d|
+ 2(?:
+ 22|
+ 3[1345789]|
+ 4[123579]|
+ 5[14]
+ )|
+ 3(?:
+ 2\d|
+ 3[1578]|
+ 4[1-35-7]|
+ 5[1-57]|
+ 61
+ )|
+ 4(?:
+ 2\d|
+ 3[1-4579]|
+ 7[1-79]
+ )|
+ 5(?:
+ 22|
+ 5[1-9]|
+ 6[1457]
+ )|
+ 6(?:
+ 22|
+ 3[12457]|
+ 4[13-8]
+ )|
+ 9(?:
+ 22|
+ 5[1-9]
+ )
+ )
+ )\d{5}
</nationalNumberPattern>
- <exampleNumber>612345678</exampleNumber>
+ <exampleNumber>662345678</exampleNumber>
</fixedLine>
<mobile>
<!-- Adding 9[45] as suggested by http://www.ucell.uz/en/for_subscribers/how_to_call.html
- -->
- <nationalNumberPattern>9[0-57-9]\d{7}</nationalNumberPattern>
+ Adding other prefixes from http://www.weltvorwahlen.de/99861353.html, and any other
+ prefixes Tyntec has a carrier mapped to. -->
+ <nationalNumberPattern>
+ 6(?:
+ 1(?:
+ 2(?:
+ 98|
+ 2[01]
+ )|
+ 35[0-4]|
+ 50\d|
+ 61[23]|
+ 7(?:
+ [01][017]|
+ 4\d|
+ 55|
+ 9[5-9]
+ )
+ )|
+ 2(?:
+ 11\d|
+ 2(?:
+ [12]1|
+ 9[01379]
+ )|
+ 5(?:
+ [126]\d|
+ 3[0-4]
+ )|
+ 7\d{2}
+ )|
+ 5(?:
+ 19[01]|
+ 2(?:
+ 27|
+ 9[26]
+ )|
+ 30\d|
+ 59\d|
+ 7\d{2}
+ )|
+ 6(?:
+ 2(?:
+ 1[5-9]|
+ 2[0367]|
+ 38|
+ 41|
+ 52|
+ 60
+ )|
+ 3[79]\d|
+ 4(?:
+ 56|
+ 83
+ )|
+ 7(?:
+ [07]\d|
+ 1[017]|
+ 3[07]|
+ 4[047]|
+ 5[057]|
+ 67|
+ 8[0178]|
+ 9[79]
+ )|
+ 9[0-3]\d
+ )|
+ 7(?:
+ 2(?:
+ 24|
+ 3[237]|
+ 4[5-9]|
+ 7[15-8]
+ )|
+ 5(?:
+ 7[12]|
+ 8[0589]
+ )|
+ 7(?:
+ 0\d|
+ [39][07]
+ )|
+ 9(?:
+ 0\d|
+ 7[079]
+ )
+ )|
+ 9(
+ 2(?:
+ 1[1267]|
+ 5\d|
+ 3[01]|
+ 7[0-4]
+ )|
+ 5[67]\d|
+ 6(?:
+ 2[0-26]|
+ 8\d
+ )|
+ 7\d{2}
+ )
+ )\d{4}|
+ 7(?:
+ 0\d{3}|
+ 1(?:
+ 13[01]|
+ 6(?:
+ 0[47]|
+ 1[67]|
+ 66
+ )|
+ 71[3-69]|
+ 98\d
+ )|
+ 2(?:
+ 2(?:
+ 2[79]|
+ 95
+ )|
+ 3(?:
+ 2[5-9]|
+ 6[0-6]
+ )|
+ 57\d|
+ 7(?:
+ 0\d|
+ 1[17]|
+ 2[27]|
+ 3[37]|
+ 44|
+ 5[057]|
+ 66|
+ 88
+ )
+ )|
+ 3(?:
+ 2(?:
+ 1[0-6]|
+ 21|
+ 3[469]|
+ 7[159]
+ )|
+ 33\d|
+ 5(?:
+ 0[0-4]|
+ 5[579]|
+ 9\d
+ )|
+ 7(?:
+ [0-3579]\d|
+ 4[0467]|
+ 6[67]|
+ 8[078]
+ )|
+ 9[4-6]\d
+ )|
+ 4(?:
+ 2(?:
+ 29|
+ 5[0257]|
+ 6[0-7]|
+ 7[1-57]
+ )|
+ 5(?:
+ 1[0-4]|
+ 8\d|
+ 9[5-9]
+ )|
+ 7(?:
+ 0\d|
+ 1[024589]|
+ 2[0127]|
+ 3[0137]|
+ [46][07]|
+ 5[01]|
+ 7[5-9]|
+ 9[079]
+ )|
+ 9(?:
+ 7[015-9]|
+ [89]\d
+ )
+ )|
+ 5(?:
+ 112|
+ 2(?:
+ 0\d|
+ 2[29]|
+ [49]4
+ )|
+ 3[1568]\d|
+ 52[6-9]|
+ 7(?:
+ 0[01578]|
+ 1[017]|
+ [23]7|
+ 4[047]|
+ [5-7]\d|
+ 8[78]|
+ 9[079]
+ )
+ )|
+ 6(?:
+ 2(?:
+ 2[1245]|
+ 4[2-4]
+ )|
+ 39\d|
+ 41[179]|
+ 5(?:
+ [349]\d|
+ 5[0-2]
+ )|
+ 7(?:
+ 0[017]|
+ [13]\d|
+ 22|
+ 44|
+ 55|
+ 67|
+ 88
+ )
+ )|
+ 9(?:
+ 22[128]|
+ 3(?:
+ 2[0-4]|
+ 7\d
+ )|
+ 57[05629]|
+ 7(?:
+ 2[05-9]|
+ 3[37]|
+ 4\d|
+ 60|
+ 7[2579]|
+ 87|
+ 9[07]
+ )
+ )
+ )\d{4}|
+ 9[0-57-9]\d{7}
+ </nationalNumberPattern>
<exampleNumber>912345678</exampleNumber>
</mobile>
<!-- No tollFree or premiumRate information can be found. -->
required PhoneNumberDesc voip = 8;
required PhoneNumberDesc pager = 21;
required PhoneNumberDesc uan = 25;
+ required PhoneNumberDesc emergency = 27;
// The rules here distinguish the numbers that are only able to be dialled
// nationally.
required PhoneNumberDesc no_international_dialling = 24;
--- /dev/null
+# Copyright (C) 2011 The Libphonenumber Authors
+
+# 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.
+
+1201|New Jersey
+1650|Kalifornien
8261|전남
8262|광주
8263|전북
-8264|제주
// String constants used to fetch the XML nodes and attributes.
private static final String CARRIER_CODE_FORMATTING_RULE = "carrierCodeFormattingRule";
private static final String COUNTRY_CODE = "countryCode";
+ private static final String EMERGENCY = "emergency";
private static final String EXAMPLE_NUMBER = "exampleNumber";
private static final String FIXED_LINE = "fixedLine";
private static final String FORMAT = "format";
PERSONAL_NUMBER));
metadata.setPager(processPhoneNumberDescElement(generalDesc, element, PAGER));
metadata.setUan(processPhoneNumberDescElement(generalDesc, element, UAN));
+ metadata.setEmergency(processPhoneNumberDescElement(generalDesc, element, EMERGENCY));
metadata.setNoInternationalDialling(processPhoneNumberDescElement(generalDesc, element,
NO_INTERNATIONAL_DIALLING));
metadata.setSameMobileAndFixedLinePattern(
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.regex.Pattern;
/**
* A utility that generates the binary serialization of the area code/location mappings from
// The path to the output directory.
private final File outputPath;
private static final int NANPA_COUNTRY_CODE = 1;
+ // Pattern used to match the two-letter-long language code contained in the input text file path.
+ private static final Pattern LANGUAGE_IN_FILE_PATH_PATTERN =
+ Pattern.compile("(.*)(?:[a-z]{2})(/\\d+\\.txt)");
+ // Map used to store the English mappings to avoid reading the English text files multiple times.
+ private final Map<Integer /* country code */, SortedMap<Integer, String>> englishMaps =
+ new HashMap<Integer, SortedMap<Integer,String>>();
private static final Logger LOGGER = Logger.getLogger(GenerateAreaCodeData.class.getName());
}
/**
+ * Returns the country code extracted from the provided text file name expected as
+ * [1-9][0-9]*.txt.
+ *
+ * @throws RuntimeException if the file path is not formatted as expected
+ */
+ private static int getCountryCodeFromTextFileName(String filename) {
+ int indexOfDot = filename.indexOf('.');
+ if (indexOfDot < 1) {
+ throw new RuntimeException(
+ String.format("unexpected file name %s, expected pattern [1-9][0-9]*.txt", filename));
+ }
+ String countryCode = filename.substring(0, indexOfDot);
+ return Integer.parseInt(countryCode);
+ }
+
+ /**
* Generates the mappings between the input text files and the output binary files.
*
* @throws IOException
continue;
}
String countryCodeFileName = countryCodeFile.getName();
- int indexOfDot = countryCodeFileName.indexOf('.');
- if (indexOfDot == -1) {
- throw new RuntimeException(
- String.format("unexpected file name %s, expected pattern .*\\.txt",
- countryCodeFileName));
- }
- String countryCode = countryCodeFileName.substring(0, indexOfDot);
- if (!countryCode.matches("\\d+")) {
- throw new RuntimeException("unexpected file " + countryCodeFileName);
- }
List<File> outputFiles = createOutputFiles(
- countryCodeFile, Integer.parseInt(countryCode), languageDirectory.getName());
+ countryCodeFile, getCountryCodeFromTextFileName(countryCodeFileName),
+ languageDirectory.getName());
mappings.put(countryCodeFile, outputFiles);
}
}
}
/**
+ * Gets the English data text file path corresponding to the provided one.
+ */
+ // @VisibleForTesting
+ static String getEnglishDataPath(File inputTextFile) {
+ return LANGUAGE_IN_FILE_PATH_PATTERN.matcher(inputTextFile.getAbsolutePath()).replaceFirst(
+ "$1en$2");
+ }
+
+ /**
+ * Tests whether any prefix of the given number overlaps with any phone number prefix contained in
+ * the provided map.
+ */
+ // @VisibleForTesting
+ static boolean hasOverlappingPrefix(int number, SortedMap<Integer, String> mappings) {
+ while (number > 0) {
+ number = number / 10;
+ if (mappings.get(number) != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Compresses the provided non-English map according to the English map provided. For each mapping
+ * which is contained in both maps with a same description this method either:
+ * <ul>
+ * <li> Removes from the non-English map the mapping whose prefix does not overlap with an
+ * existing prefix in the map, or;
+ * <li> Keeps this mapping in both maps but makes the description an empty string in the
+ * non-English map.
+ * </ul>
+ */
+ // @VisibleForTesting
+ static void compressAccordingToEnglishData(
+ SortedMap<Integer, String> englishMap, SortedMap<Integer, String> nonEnglishMap) {
+ Iterator<Map.Entry<Integer, String>> it = nonEnglishMap.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, String> entry = it.next();
+ int prefix = entry.getKey();
+ String englishDescription = englishMap.get(prefix);
+ if (englishDescription != null && englishDescription.equals(entry.getValue())) {
+ if (!hasOverlappingPrefix(prefix, nonEnglishMap)) {
+ it.remove();
+ } else {
+ nonEnglishMap.put(prefix, "");
+ }
+ }
+ }
+ }
+
+ /**
+ * Compresses the provided mappings according to the English data file if any.
+ *
+ * @throws IOException
+ */
+ private void makeDataFallbackToEnglish(File inputTextFile, SortedMap<Integer, String> mappings)
+ throws IOException {
+ File englishTextFile = new File(getEnglishDataPath(inputTextFile));
+ if (inputTextFile.getAbsolutePath().equals(englishTextFile.getAbsolutePath()) ||
+ !englishTextFile.exists()) {
+ return;
+ }
+ int countryCode = getCountryCodeFromTextFileName(inputTextFile.getName());
+ SortedMap<Integer, String> englishMap = englishMaps.get(countryCode);
+ if (englishMap == null) {
+ FileInputStream englishFileInputStream = null;
+ try {
+ englishFileInputStream = new FileInputStream(englishTextFile);
+ englishMap = readMappingsFromTextFile(englishFileInputStream);
+ englishMaps.put(countryCode, englishMap);
+ } finally {
+ closeFile(englishFileInputStream);
+ }
+ }
+ compressAccordingToEnglishData(englishMap, mappings);
+ }
+
+ /**
+ * Removes the empty-description mappings in the provided map if the language passed-in is "en".
+ */
+ // @VisibleForTesting
+ static void removeEmptyEnglishMappings(SortedMap<Integer, String> map, String lang) {
+ if (!lang.equals("en")) {
+ return;
+ }
+ Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<Integer, String> mapping = it.next();
+ if (mapping.getValue().isEmpty()) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
* Runs the area code data generator.
*
* @throws IOException
- * @throws FileNotFoundException
*/
public void run() throws IOException {
Map<File, List<File>> inputOutputMappings = createInputOutputMappings();
List<File> outputBinaryFiles = inputOutputMapping.getValue();
fileInputStream = new FileInputStream(textFile);
SortedMap<Integer, String> mappings = readMappingsFromTextFile(fileInputStream);
+ removeEmptyEnglishMappings(mappings, textFile.getParentFile().getName());
+ makeDataFallbackToEnglish(textFile, mappings);
Map<File, SortedMap<Integer, String>> mappingsForFiles =
splitMap(mappings, outputBinaryFiles);
// Expected.
}
}
+
+ public void testGetEnglishDataPath() {
+ assertEquals("/path/en/33.txt",
+ GenerateAreaCodeData.getEnglishDataPath(new File("/path/fr/33.txt")));
+ }
+
+ public void testHasOverlap() {
+ SortedMap<Integer, String> map = new TreeMap<Integer, String>();
+ map.put(1234, "");
+ map.put(123, "");
+ map.put(2345, "");
+
+ assertTrue(GenerateAreaCodeData.hasOverlappingPrefix(1234, map));
+ assertFalse(GenerateAreaCodeData.hasOverlappingPrefix(2345, map));
+ }
+
+ public void testCompressAccordingToEnglishDataMakesDescriptionEmpty() {
+ SortedMap<Integer, String> frenchMappings = new TreeMap<Integer, String>();
+ frenchMappings.put(411, "Genève");
+ frenchMappings.put(4112, "Zurich");
+
+ SortedMap<Integer, String> englishMappings = new TreeMap<Integer, String>();
+ englishMappings.put(411, "Geneva");
+ englishMappings.put(4112, "Zurich");
+ // The English map should not be modified.
+ englishMappings = Collections.unmodifiableSortedMap(englishMappings);
+
+ GenerateAreaCodeData.compressAccordingToEnglishData(englishMappings, frenchMappings);
+
+ assertEquals(2, frenchMappings.size());
+ assertEquals("Genève", frenchMappings.get(411));
+ assertEquals("", frenchMappings.get(4112));
+ }
+
+ public void testCompressAccordingToEnglishDataRemovesMappingWhenNoOverlap() {
+ SortedMap<Integer, String> frenchMappings = new TreeMap<Integer, String>();
+ frenchMappings.put(411, "Genève");
+ frenchMappings.put(412, "Zurich");
+
+ SortedMap<Integer, String> englishMappings = new TreeMap<Integer, String>();
+ englishMappings.put(411, "Geneva");
+ englishMappings.put(412, "Zurich");
+ // The English map should not be modified.
+ englishMappings = Collections.unmodifiableSortedMap(englishMappings);
+
+ GenerateAreaCodeData.compressAccordingToEnglishData(englishMappings, frenchMappings);
+
+ assertEquals(1, frenchMappings.size());
+ assertEquals("Genève", frenchMappings.get(411));
+ }
+
+ public void testCompressAccordingToEnglishData() {
+ SortedMap<Integer, String> frenchMappings = new TreeMap<Integer, String>();
+ frenchMappings.put(12, "A");
+ frenchMappings.put(123, "B");
+
+ SortedMap<Integer, String> englishMappings = new TreeMap<Integer, String>();
+ englishMappings.put(12, "A");
+ englishMappings.put(123, "B");
+ // The English map should not be modified.
+ englishMappings = Collections.unmodifiableSortedMap(englishMappings);
+
+ GenerateAreaCodeData.compressAccordingToEnglishData(englishMappings, frenchMappings);
+
+ assertEquals(0, frenchMappings.size());
+ }
+
+ public void testRemoveEmptyEnglishMappingsDoesNotRemoveNonEnglishMappings() {
+ SortedMap<Integer, String> frenchMappings = new TreeMap<Integer, String>();
+ frenchMappings.put(331, "Paris");
+ frenchMappings.put(334, "");
+ // The French map should not be modified.
+ frenchMappings = Collections.unmodifiableSortedMap(frenchMappings);
+
+ GenerateAreaCodeData.removeEmptyEnglishMappings(frenchMappings, "fr");
+
+ assertEquals(2, frenchMappings.size());
+ }
+
+ public void testRemoveEmptyEnglishMappings() {
+ SortedMap<Integer, String> englishMappings = new TreeMap<Integer, String>();
+ englishMappings.put(331, "Paris");
+ englishMappings.put(334, "");
+
+ GenerateAreaCodeData.removeEmptyEnglishMappings(englishMappings, "en");
+
+ assertEquals(1, englishMappings.size());
+ assertEquals("Paris", englishMappings.get(331));
+ }
}