From 72660ad60c650741a72b2b5e987b5ebe2f362c79 Mon Sep 17 00:00:00 2001 From: Shaopeng Jia Date: Mon, 12 Jul 2010 12:18:43 +0000 Subject: [PATCH] Update library with new functionalities and metadata. --- .../BuildMetadataProtoFromXml.java | 31 + .../phonenumbers/proto/phonemetadata.proto | 23 +- .../phonenumbers/src/PhoneNumberMetaData.xml | 602 +++++++++++++++--- .../test/PhoneNumberMetaDataForTesting.xml | 63 +- .../i18n/phonenumbers/AsYouTypeFormatter.java | 113 ++-- .../phonenumbers/PhoneNumberMetadataProto | Bin 90306 -> 97324 bytes .../i18n/phonenumbers/PhoneNumberUtil.java | 334 +++++----- .../i18n/phonenumbers/Phonemetadata.java | 56 ++ .../phonenumbers/AsYouTypeFormatterTest.java | 201 +++--- .../PhoneNumberMetadataProtoForTesting | Bin 6201 -> 6959 bytes .../phonenumbers/PhoneNumberUtilTest.java | 110 +++- 11 files changed, 1090 insertions(+), 443 deletions(-) diff --git a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java b/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java index 272ba29d1..2d2a50d99 100644 --- a/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java +++ b/java/resources/com/google/i18n/phonenumbers/BuildMetadataProtoFromXml.java @@ -98,6 +98,9 @@ public class BuildMetadataProtoFromXml { PhoneMetadata metadata = new PhoneMetadata(); metadata.setId(regionCode); metadata.setCountryCode(Integer.parseInt(element.getAttribute("countryCode"))); + if (element.hasAttribute("leadingDigits")) { + metadata.setLeadingDigits(validateRE(element.getAttribute("leadingDigits"))); + } metadata.setInternationalPrefix(validateRE(element.getAttribute("internationalPrefix"))); if (element.hasAttribute("preferredInternationalPrefix")) { String preferredInternationalPrefix = element.getAttribute("preferredInternationalPrefix"); @@ -105,6 +108,7 @@ public class BuildMetadataProtoFromXml { } String nationalPrefix = ""; String nationalPrefixFormattingRule = ""; + String carrierCodeFormattingRule = ""; if (element.hasAttribute("nationalPrefix")) { nationalPrefix = element.getAttribute("nationalPrefix"); metadata.setNationalPrefix(nationalPrefix); @@ -126,6 +130,10 @@ public class BuildMetadataProtoFromXml { metadata.setPreferredExtnPrefix(element.getAttribute("preferredExtnPrefix")); } + if (element.hasAttribute("mainCountryForCode")) { + metadata.setMainCountryForCode(true); + } + // Extract availableFormats NodeList numberFormatElements = element.getElementsByTagName("numberFormat"); int numOfFormatElements = numberFormatElements.getLength(); @@ -139,6 +147,13 @@ public class BuildMetadataProtoFromXml { } else { format.setNationalPrefixFormattingRule(nationalPrefixFormattingRule); } + if (numberFormatElement.hasAttribute("carrierCodeFormattingRule")) { + format.setDomesticCarrierCodeFormattingRule(validateRE( + getDomesticCarrierCodeFormattingRuleFromElement(numberFormatElement, + nationalPrefix))); + } else { + format.setDomesticCarrierCodeFormattingRule(carrierCodeFormattingRule); + } if (numberFormatElement.hasAttribute("leadingDigits")) { format.setLeadingDigits(validateRE(numberFormatElement.getAttribute("leadingDigits"))); } @@ -159,6 +174,13 @@ public class BuildMetadataProtoFromXml { } format.setPattern(validateRE(numberFormatElement.getAttribute("pattern"))); format.setFormat(validateRE(numberFormatElement.getFirstChild().getNodeValue())); + if (numberFormatElement.hasAttribute("carrierCodeFormattingRule")) { + format.setDomesticCarrierCodeFormattingRule(validateRE( + getDomesticCarrierCodeFormattingRuleFromElement(numberFormatElement, + nationalPrefix))); + } else { + format.setDomesticCarrierCodeFormattingRule(carrierCodeFormattingRule); + } metadata.addIntlNumberFormat(format); } } @@ -192,6 +214,15 @@ public class BuildMetadataProtoFromXml { return nationalPrefixFormattingRule; } + private static String getDomesticCarrierCodeFormattingRuleFromElement(Element element, + String nationalPrefix) { + String carrierCodeFormattingRule = element.getAttribute("carrierCodeFormattingRule"); + // Replace $FG with the first group ($1) and $NP with the national prefix. + carrierCodeFormattingRule = carrierCodeFormattingRule.replaceFirst("\\$FG", "\\$1") + .replaceFirst("\\$NP", nationalPrefix); + return carrierCodeFormattingRule; + } + /** * Processes a phone number description element from the XML file and returns it as a * PhoneNumberDesc. If the description element is a fixed line or mobile number, the general diff --git a/java/resources/com/google/i18n/phonenumbers/proto/phonemetadata.proto b/java/resources/com/google/i18n/phonenumbers/proto/phonemetadata.proto index 44f9764f5..6c30e14b4 100644 --- a/java/resources/com/google/i18n/phonenumbers/proto/phonemetadata.proto +++ b/java/resources/com/google/i18n/phonenumbers/proto/phonemetadata.proto @@ -64,6 +64,12 @@ message NumberFormat { // prefix in NATIONAL format. This field does not affect how a number // is formatted in other formats, such as INTERNATIONAL. optional string national_prefix_formatting_rule = 4; + + // This field specifies how any carrier code ($CC) together with the first + // group ($FG) in the national significant number should be formatted + // when formatWithCarrierCode is called, if carrier codes are used for a + // certain country. + optional string domestic_carrier_code_formatting_rule = 5; } message PhoneNumberDesc { @@ -193,8 +199,21 @@ message PhoneMetadata { // 15 (inserted after the area code of 343) is used. repeated NumberFormat intl_number_format = 20; - // Deprecated. - optional string national_prefix_formatting_rule = 21; + // This field is set when this country is considered to be the main country + // for a calling code. It may not be set by more than one country with the + // same calling code, and it should not be set by countries with a unique + // calling code. This can be used to indicate that "GB" is the main country + // for the calling code "44" for example, rather than Jersey or the Isle of + // Man. + optional bool main_country_for_code = 22 [default=false]; + + // This field is populated only for countries or regions that share a country + // calling code. If a number matches this pattern, it could belong to this + // region. This is not intended as a replacement for IsValidForRegion, and + // does not mean the number must come from this region (for example, 800 + // numbers are valid for all NANPA countries.) This field should be a regular + // expression of the expected prefix match. + optional string leading_digits = 23; } message PhoneMetadataCollection { diff --git a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml index a463f4952..1958cea2c 100644 --- a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml +++ b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml @@ -53,6 +53,7 @@ + @@ -60,11 +61,15 @@ + + + + ]> @@ -174,7 +179,8 @@ - + [289]\d{9} @@ -209,7 +215,8 @@ - + [289]\d{9} @@ -391,7 +398,8 @@ - + [689]\d{9} @@ -605,13 +613,50 @@ + + + nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG"> + + $1 $2-$3 + + + [3-689]\d{7} + \d{6,8} + + + + (?:[35]\d|49|81)\d{6} + 30123456 + + + 6[1-356]\d{6} + \d{8} + 61123456 + + + 8[08]\d{6} + \d{8} + 80123456 + + + 9[0246]\d{6} + \d{8} + 90123456 + + + + 82\d{6} + \d{8} + 82123456 + - + [289]\d{9} @@ -639,8 +684,51 @@ - + + + + + + $1 $2 + $1 $2 + $1 $2 + $1 $2 + + + + [2-79]\d{5,9}|1\d{9}|8[0-7]\d{4,8} + \d{6,10} + + + + 2(?:7\d1|8(?:[026]1|[1379][1-5]|8[1-8])|9(?:0[0-2]|1[1-4]|3[3-5]|5[56]|6[67]|71|8[078]))\d{4}|3(?:[6-8]1|(?:0[23]|[25][12]|82|416)\d|(?:31|12?[5-7])\d{2})\d{3}|4(?:(?:02|[49]6|[68]1)|(?:0[13]|21\d?|[23]2|[457][12]|6[28])\d|(?:23|[39]1)\d{2}|1\d{3})\d{3}|5(?:(?:[457-9]1|62)|(?:1\d?|2[12]|3[1-3]|52)\d|61{2})|6(?:[45]1|(?:11|2[15]|[39]1)\d|(?:[06-8]1|62)\d{2})|7(?:(?:32|91)|(?:02|31|[67][12])\d|[458]1\d{2}|21\d{3})\d{3}|8(?:(?:4[12]|[5-7]2|1\d?)|(?:0|3[12]|[5-7]1|217)\d)\d{4}|9(?:[35]1|(?:[024]2|81)\d|(?:1|[24]1)\d{2})\d{3} + \d{6,9} + 27111234 + + + + (?:1[13-9]\d|(?:3[78]|44)[02-9]|6(?:44|6[02-9]))\d{7} + \d{10} + 1812345678 + + + + 80[03]\d{7} + \d{10} + 8001234567 + @@ -711,7 +799,7 @@ - 7(?:[01568]\d|2[0-4]|4[01]|5[01346-9])\d{5} + 7(?:[04-6]\d|1[0-489]|2[0-8]|8[013-9]|90)\d{5} 70123456 @@ -830,13 +918,36 @@ - - + + + + + + + [56]\d{8} + \d{9} + + + 590(?:2[7-9]|5[12]|87)\d{4} + 590271234 + + + 690(?:10|2[27]|66|77|8[78])\d{4} + 690221234 + - + [489]\d{9} @@ -904,7 +1015,9 @@ code.--> ($1) $2-$3 + pattern="(\d{2})(\d{4})(\d{4})" + nationalPrefixFormattingRule="($FG)" + carrierCodeFormattingRule="$NP $CC $FG">$1 $2-$3 $1-$2 $1 $2 $3 @@ -938,7 +1051,8 @@ - + [289]\d{9} @@ -1029,8 +1143,46 @@ + + + nationalPrefixForParsing="80?" nationalPrefix="8"> + + $1 $2 $3 + $1 $2 $3 + + + [12-4]\d{8}|[89]\d{9} + + \d{7,10} + + + (?:1(?:5(?:1[1-5]|2\d|6[1-4]|9[1-7])|6(?:[235]\d|4[1-7])|7\d{2})|2(?:1(?:[246]\d|3[0-35-9]|5[1-9])|2(?:[235]\d|4[0-8])|3(?:2\d|3[02-79]|4[024-7]|5[0-7])))\d{5} + \d{7,9} + + 152450911 + + + (?:2(?:5[679]|9[1-9])|33\d|44\d)\d{6} + \d{9} + + 294911911 + + + + 80[13]\d{7} + \d{10} + 8011234567 + + + 902\d{7} + \d{10} + 9021234567 + @@ -1377,7 +1529,23 @@ + + + $1 $2 + + + [28]\d{7} + \d{8} + + + 2[24-7]\d{6} + 22123456 + + + 8[38]\d{6} + 83123456 + @@ -1503,8 +1671,9 @@ $1 $2 $1 $2 $3 $1 $2 $3 - $1 $2 $3 + $1 $2 + $1 $2 $3 $1 $2 $3 @@ -1529,8 +1698,8 @@ 15123456789 - 800\d{7} - \d{10} + 800\d{7,9} + \d{10,12} 8001234567 @@ -1609,7 +1778,8 @@ - + [7-9]\d{9} @@ -1637,18 +1807,24 @@ - + + [89]\d{9} \d{7,10} - 8[02]9[2-9]\d{6} + + 8[024]9[2-9]\d{6} 8092345678 - 8[02]9[2-9]\d{6} + 8[024]9[2-9]\d{6} 8092345678 @@ -1771,6 +1947,9 @@ + $1 $2 $1 $2 $1 $2 $3 - [1-689]\d{7,9} - \d{7,10} + 1\d{4,9}|[2-689]\d{7,9} + \d{5,10} - (?:1[35][23]|2[23]\d|3\d|4(?:0[2-4]|[578][23]|64)|5(?:0[234]|[57][23])|6[24-689]3|8(?:[28][2-4]|42|6[23])|9(?:[25]2|3[24]|6[23]|7[2-4]))\d{6} - \d{7,9} + + (?:1[35][23]|2[23]\d|3\d|4(?:0[2-4]|[578][23]|64)|5(?:0[234]|[57][23])|6[24-689]3|8(?:[28][2-4]|42|6[23])|9(?:[25]2|3[24]|6[23]|7[2-4]))\d{6}|1[69]\d{3} + \d{5,9} 234567890 @@ -1950,13 +2131,13 @@ - $1 $2 $3 $4 $5 + $1 $2 $3 $4 $5 $1 $2 $3 $4 - [1-689]\d{8} + [1-9]\d{8} \d{9} @@ -2018,7 +2199,8 @@ + nationalPrefix="0" preferredExtnPrefix=" x" nationalPrefixFormattingRule="$NP$FG" + mainCountryForCode="true" > $1 $2 $3 @@ -2096,7 +2278,8 @@ - + [489]\d{9} @@ -2302,29 +2485,61 @@ - + + + + $1 $2-$3 + + + [56]\d{8} + \d{9} + + + + 590(?:1[12]|2[0-68]|3[28]|4[126-8]|5[067]|6[018]|[89]\d)\d{4} + 590201234 + + + 690(?:00|[3-5]\d|6[0-57-9]|7[1-6]|8[0-6]|9[09])\d{4} + 690301234 + - - $1 $2 $3 + $1 $2 $3 + $1 $2 - [0256]\d{5} - \d{6} + [23589]\d{8} + + \d{6,9} - 0[46-9]\d{4} - 041234 + 3(?:3(?:3\d[7-9]|[0-24-9]\d[46])|5\d{2}[7-9])\d{4} + 333091234 - [256]\d{5} - 212345 + (?:222|551)\d{6} + 222123456 + + + 80\d[1-9]\d{5} + 800123456 + + + 90\d[1-9]\d{5} + 900123456 + @@ -2377,7 +2592,8 @@ - + [689]\d{9} @@ -2476,6 +2692,7 @@ + @@ -2483,25 +2700,26 @@ websites are formatted 1 XXXX XXX, so we prefer that formatting here. These same sources prefer XXX XXX to XX XXXX as well. --> $1 $2 $3 - $1 $2 $3 + $1 $2 $3 $1 $2 $3 $1 $2 $3 $1 $2 $3 $4 - $1 $2 $3 - $1 $2 - $1 $2 $3 - $1 $2 $3 + $1 $2 $3 + $1 $2 $3 + $1 $2 $3 + $1 $2 $3 [1-7]\d{5,8}|[89]\d{6,11} \d{6,12} - - (?:1|62)\d{7}|(?:2[0-3]|3[1-5]|4[02-47-9]|5[1-3])\d{6} + + (?:1|6[029])\d{7}|(?:2[0-3]|3[1-5]|4[02-47-9]|5[1-3])\d{6} \d{6,9} 12345678 @@ -2511,19 +2729,22 @@ 912345678 - - 800\d{4,7} + 80[01]\d{4,7} \d{7,10} 8001234567 - 6(?:0\d{3}|1)\d{4} + rates to premium rate so we include them here. --> + 6[145]\d{4,7} \d{6,9} - 601234567 + 611234 + + 7[45]\d{4,7} + \d{6,9} + 741234567 + @@ -3080,7 +3301,8 @@ - + [89]\d{9} @@ -3162,36 +3384,56 @@ - - - + - $1 $2 $3 + + $1 $2 $3 + $1 $2 $3 - - 154-20, 15472 -> 1547-2, 15410 -> 15-410, + - "22": 22200 -> 22-200, 22300 -> 22-300, 22320 -> 223-20, 22350 -> 22-350 + - "42": 42000 -> 4-2000, 42901 -> 4-2901, 42910 -> 42-910 + - "82": 82200 -> 82-200, 82020 -> 820-20, 82400 -> 82-400 + - "99": 99400 -> 99-400, 99430 -> 994-30, 99692 -> 9969-2, 99750 -> 997-50 + - "993": 99330 -> 993-30, 99331 -> 99-331, 99332 -> 993-32 + --> + $1 $2 $3 - - $1 $2 $3 - - $1 $2 $3 + $1 $2 $3 + $1 $2 $3 + $1 $2 $3 + $1 $2 $3 - $1 $2 $3 \d{9,10} \d{9,10} - (?:(?:1[1-9]|9[2-9])[1-9]|(?:[36][1-9]|[24578][2-9])\d)\d{6} + (?:1[1-9][1-9]|9(?:[3-9][1-9]|2\d)|(?:[36][1-9]|[24578][2-9])\d)\d{6} \d{9} 312345678 @@ -3365,7 +3607,8 @@ - + [89]\d{9} @@ -3503,7 +3746,8 @@ - + [389]\d{9} @@ -3534,10 +3778,7 @@ - - $1 $2 $3 - + (?:[67]\d{2}|80[09])\d{7} \d{10} @@ -3634,7 +3875,8 @@ - + [789]\d{9} @@ -3845,7 +4087,41 @@ - + + + + + $1-$2 + $1-$2 + $1-$2 + $1-$2 + + + [5689]\d{8} + + \d{9} + + + 5(?:2(?:[015-7]\d{2}|(?:[28][2-9]|3[2-7]|4[2-8])\d|9(?:0\d|[89]0))|3(?:[0-4]\d{2}|(?:[57][2-9]|6[2-8]|9[3-9])\d|8(?:0\d|[89]0)))\d{4} + 520123456 + + + 6(?:00|33|[15-7]\d|4[0-8]|99)\d{6} + 650123456 + + + 80\d{7} + 801234567 + + + 89\d{7} + 891234567 + @@ -3921,6 +4197,25 @@ + + + + + + [56]\d{8} + \d{9} + + + 590(?:10|2[79]|5[128]|[78]7)\d{4} + 590271234 + + + 690(?:10|2[27]|66|77|8[78])\d{4} + 690221234 + + + @@ -3979,13 +4274,12 @@ \d{8} - + (?:2(?:0(?:2[0-589]|7[027-9])|1(?:2[5-7]|[3-689]\d))|442\d)\d{4} 20212345 - (?:6(?:[569]\d)|7(?:[3579][0-4]|4[014-7]|6\d|8[1-9]))\d{5} + (?:6(?:[569]\d)|7(?:[08][1-9]|[3579][0-4]|4[014-7]|6\d))\d{5} 65012345 @@ -4088,7 +4382,8 @@ - + [689]\d{9} @@ -4142,7 +4437,8 @@ - + [689]\d{9} @@ -4759,7 +5055,7 @@ $1 $2 - (?:2[3-6]|5|9[235-9])\d{6}|800\d{5,6} + (?:2[3-6]|5|9[2-9])\d{6}|800\d{5,6} \d{7,9} @@ -4768,9 +5064,7 @@ 23123456 - - 9[235-9]\d{6} + 9[2-9]\d{6} \d{8} 92123456 @@ -4795,8 +5089,35 @@ + + + nationalPrefix="0" nationalPrefixFormattingRule="($FG)" + preferredExtnPrefix=" Anexo "> + + $1 $2 $3 + $1 $2 + $1 $2 + $1 $2 $3 + + + + [146-8]\d{7,10}|5\d{7}(?:\d{3})? + \d{7,11} + + + (?:1\d{2}|4[1-4]|5[1-46]|6[1-7]|7[2-46]|8[2-4])\d{6} + \d{7,9} + 112345678 + + + (?:1|4[1-4]|5[1-46]|6[1-7]|7[2-46]|8[2-4])9\d{8} + \d{10,11} + 54951234567 + @@ -4981,7 +5302,8 @@ - + [789]\d{9} @@ -5085,7 +5407,8 @@ them. --> + nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG" + leadingDigits="262|6[49]|8" mainCountryForCode="true" > $1 $2 $3 $4 @@ -5206,11 +5529,16 @@ + nationalPrefix="8" nationalPrefixFormattingRule="$NP ($FG)" + mainCountryForCode="true" > - $1 $2-$3-$4 + as http://www.minjust.ru/ru/structure/contact/. Contains formatting + instructions for Kazakhstan as well. --> + $1 $2-$3-$4 + $1 $2 $3 [3489]\d{9} @@ -5714,7 +6042,8 @@ - + [689]\d{9} @@ -5916,8 +6245,29 @@ - + + + + $1 $2 $3 + + + [247-9]\d{7} + \d{8} + + + 7\d{7} + 71234567 + + + (?:2[0-7]|40|9\d)\d{6} + 20123456 + + + + 8[028]\d{6} + 80123456 + @@ -5956,7 +6306,8 @@ - + [89]\d{9} @@ -6163,7 +6514,8 @@ numbers in the national format, it is not included. Therefore, we omit it here to make formatting consistent with the rest of the world. The same applies to all the countries/regions under NANPA --> - + ($1) $2-$3 $1-$2 @@ -6254,7 +6606,8 @@ - + (?:784|8(?:00|66|77|88)|900)[2-9]\d{6} @@ -6282,13 +6635,48 @@ + + + + nationalPrefix="0" nationalPrefixForParsing="1\d{2}|0" + nationalPrefixFormattingRule="$NP$FG"> + + $1-$2 + + + [24589]\d{9} + + \d{7,10} + + + + (?:2(?:12|3[457-9]|[58][1-9]|[467]\d|9[1-6])|50[01])\d{7} + 2121234567 + + + 4(?:1[24-8]|2[46])\d{7} + \d{10} + 4121234567 + + + 800\d{7} + \d{10} + 8001234567 + + + 900\d{7} + \d{10} + 9001234567 + - + (?:284|8(?:00|66|77|88)|900)[2-9]\d{6} @@ -6317,7 +6705,8 @@ - + 340(?:6[49]2|7[17]\d)\d{4}|(?:8(?:00|66|77|88)|900)[2-9]\d{6} @@ -6475,7 +6864,8 @@ fixed-line prefixes, but the mobile prefixes listed here seem out of date. --> + nationalPrefix="0" nationalPrefixFormattingRule="$NP$FG" + leadingDigits="269|63"> [268]\d{8} diff --git a/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml b/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml index 524507bbf..e6d11cb46 100644 --- a/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml +++ b/java/resources/com/google/i18n/phonenumbers/test/PhoneNumberMetaDataForTesting.xml @@ -38,7 +38,7 @@ $1 $2-$3 $1 $2-$3 $1 15 $2-$3 - $1 15 $2-$3 + $1 $2-$3 $1-$2-$3 $1 $2-$3 $1 $2-$3 @@ -370,6 +370,41 @@ + + + + + $1 $2 $3 $4 + + + [268]\d{8} + \d{9} + + + + 262\d{6} + 262161234 + + + 6(?:9[23]|47)\d{6} + \d{9} + 692123456 + + + + 80\d{7} + 801234567 + + + 8(?:1[01]|2[0156]|84|9[0-37-9])\d{6} + 810123456 + + + @@ -405,7 +440,8 @@ + preferredExtnPrefix=" extn. " + mainCountryForCode="true"> $1 $2 $3 $1 $2 @@ -424,5 +460,28 @@ \d{10} + + + + + + [268]\d{8} + \d{9} + + + 2696[0-4]\d{4} + 269601234 + + + 639\d{6} + 639123456 + + + + 80\d{7} + 801234567 + + diff --git a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java index 8936d5b49..303a4744c 100644 --- a/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java +++ b/java/src/com/google/i18n/phonenumbers/AsYouTypeFormatter.java @@ -49,23 +49,31 @@ public class AsYouTypeFormatter { private Phonemetadata.PhoneMetadata defaultMetaData; private PhoneMetadata currentMetaData; + // A pattern that is used to match character classes in regular expressions. An example of a + // character class is [1-4]. + private final Pattern CHARACTER_CLASS_PATTERN = Pattern.compile("\\[([^\\[\\]])*\\]"); + // Any digit in a regular expression that actually denotes a digit. For example, in the regular + // expression 80[0-2]\d{6,10}, the first 2 digits (8 and 0) are standalone digits, but the rest + // are not. + // Two look-aheads are needed because the number following \\d could be a two-digit number, since + // the phone number can be as long as 15 digits. + private static final Pattern STANDALONE_DIGIT_PATTERN = Pattern.compile("\\d(?=[^,}][^,}])"); + // The digits that have not been entered yet will be represented by a \u2008, the punctuation // space. private String digitPlaceholder = "\u2008"; private Pattern digitPattern = Pattern.compile(digitPlaceholder); private int lastMatchPosition = 0; - private boolean rememberPosition = false; + // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as + // found in the current output. private int positionRemembered = 0; + // The position of a digit upon which inputDigitAndRememberPosition is most recently invoked, as + // found in the original sequence of characters the user entered. private int originalPosition = 0; private Pattern nationalPrefixForParsing; private Pattern internationalPrefix; private StringBuffer prefixBeforeNationalNumber; private StringBuffer nationalNumber; - // No formatting will be applied when any of the character in the following character class is - // entered by users. - private final Pattern UNSUPPORTED_SYNTAX = Pattern.compile("[- *#;,.()/a-zA-Z]"); - private final Pattern CHARACTER_CLASS_PATTERN = Pattern.compile("\\[([^\\[\\]])*\\]"); - private final Pattern STANDALONE_DIGIT_PATTERN = Pattern.compile("\\d(?=[^,}][^,}])"); /** * Constructs a light-weight formatter which does no formatting, but outputs exactly what is @@ -182,25 +190,32 @@ public class AsYouTypeFormatter { } } + public String inputDigit(char nextChar) { + return inputDigitWithOptionToRememberPosition(nextChar, false); + } + /** - * Formats a phone number on-the-fly as each digit is entered. - * - * @param nextChar the most recently entered digit of a phone number. Formatting characters are - * allowed, but they are removed from the result. Full width digits and Arabic-indic digits - * are allowed, and will be shown as they are. - * @return the partially formatted phone number. + * Same as inputDigit, but remembers the position where nextChar is inserted, so that it could be + * retrieved later by using getRememberedPosition(). The remembered position will be automatically + * adjusted if additional formatting characters are later inserted/removed in front of nextChar. */ - public String inputDigit(char nextChar) { + public String inputDigitAndRememberPosition(char nextChar) { + return inputDigitWithOptionToRememberPosition(nextChar, true); + } + + private String inputDigitWithOptionToRememberPosition(char nextChar, boolean rememberPosition) { accruedInput.append(nextChar); - rememberPosition(); - if (UNSUPPORTED_SYNTAX.matcher(Character.toString(nextChar)).matches()) { + if (rememberPosition) { + positionRemembered = accruedInput.length(); + originalPosition = positionRemembered; + } + // We do formatting on-the-fly only when each character entered is either a plus sign or a + // digit. + if (!PhoneNumberUtil.VALID_START_CHAR_PATTERN.matcher(Character.toString(nextChar)).matches()) { ableToFormat = false; } if (!ableToFormat) { - if (positionRemembered > 0 && currentOutput.length() > 0) { - positionRemembered = originalPosition; - currentOutput.setLength(0); - } + resetPositionOnFailureToFormat(); return accruedInput.toString(); } @@ -222,38 +237,26 @@ public class AsYouTypeFormatter { return accruedInput.toString(); } removeNationalPrefixFromNationalNumber(); - return attemptToChooseFormattingPattern(); + return attemptToChooseFormattingPattern(rememberPosition); default: if (nationalNumber.length() > 4) { // The formatting pattern is already chosen. - String temp = inputDigitHelper(nextChar); + String temp = inputDigitHelper(nextChar, rememberPosition); return ableToFormat ? prefixBeforeNationalNumber + temp : temp; } else { - return attemptToChooseFormattingPattern(); + return attemptToChooseFormattingPattern(rememberPosition); } } } - private void rememberPosition() { - if (rememberPosition) { - positionRemembered = accruedInput.length(); - originalPosition = positionRemembered; + private void resetPositionOnFailureToFormat() { + if (positionRemembered > 0) { + positionRemembered = originalPosition; + currentOutput.setLength(0); } } - /** - * Same as inputDigit, but remembers the position where nextChar is inserted, so that it could be - * retrieved later by using getRememberedPosition(). The remembered position will be automatically - * adjusted if additional formatting characters are later inserted/removed in front of nextChar. - */ - public String inputDigitAndRememberPosition(char nextChar) { - rememberPosition = true; - String result = inputDigit(nextChar); - rememberPosition = false; - return result; - } - /** * Returns the current position in the partially formatted phone number of the character which was * previously passed in as the parameter of inputDigitAndRememberPosition(). @@ -264,12 +267,12 @@ public class AsYouTypeFormatter { // Attempts to set the formatting template and returns a string which contains the formatted // version of the digits entered so far. - private String attemptToChooseFormattingPattern() { + private String attemptToChooseFormattingPattern(boolean rememberPosition) { // We start to attempt to format only when as least 4 digits of national number (excluding // national prefix) have been entered. if (nationalNumber.length() >= 4) { chooseFormatAndCreateTemplate(nationalNumber.substring(0, 4)); - return inputAccruedNationalNumber(); + return inputAccruedNationalNumber(rememberPosition); } else { if (rememberPosition) { positionRemembered = prefixBeforeNationalNumber.length() + nationalNumber.length(); @@ -280,23 +283,23 @@ public class AsYouTypeFormatter { // Invokes inputDigitHelper on each digit of the national number accrued, and returns a formatted // string in the end. - private String inputAccruedNationalNumber() { + private String inputAccruedNationalNumber(boolean rememberPosition) { int lengthOfNationalNumber = nationalNumber.length(); if (lengthOfNationalNumber > 0) { // The positionRemembered should be only adjusted once in the loop that follows. - Boolean positionAlreadyAdjusted = false; - for (int i = 0; i < lengthOfNationalNumber - 1; i++) { - String temp = inputDigitHelper(nationalNumber.charAt(i)); + boolean positionAlreadyAdjusted = false; + String tempNationalNumber = ""; + for (int i = 0; i < lengthOfNationalNumber; i++) { + tempNationalNumber = inputDigitHelper(nationalNumber.charAt(i), rememberPosition); if (!positionAlreadyAdjusted && positionRemembered - prefixBeforeNationalNumber.length() == i + 1) { - positionRemembered = prefixBeforeNationalNumber.length() + temp.length(); + positionRemembered = prefixBeforeNationalNumber.length() + tempNationalNumber.length(); positionAlreadyAdjusted = true; } } - String temp = inputDigitHelper(nationalNumber.charAt(lengthOfNationalNumber - 1)); return ableToFormat - ? prefixBeforeNationalNumber + temp - : temp; + ? prefixBeforeNationalNumber + tempNationalNumber + : tempNationalNumber; } else { if (rememberPosition) { positionRemembered = prefixBeforeNationalNumber.length(); @@ -309,13 +312,12 @@ public class AsYouTypeFormatter { int startOfNationalNumber = 0; if (currentMetaData.getCountryCode() == 1 && nationalNumber.charAt(0) == '1') { startOfNationalNumber = 1; - prefixBeforeNationalNumber.append("1"); - // Since a space will be inserted after the national prefix in this case, we increase the + prefixBeforeNationalNumber.append("1 "); + // Since a space is inserted after the national prefix in this case, we increase the // remembered position by 1 for anything that is after the national prefix. - if (positionRemembered > prefixBeforeNationalNumber.length()) { + if (positionRemembered > prefixBeforeNationalNumber.length() - 1) { positionRemembered++; } - prefixBeforeNationalNumber.append(" "); } else if (currentMetaData.hasNationalPrefix()) { Matcher m = nationalPrefixForParsing.matcher(nationalNumber); if (m.lookingAt()) { @@ -392,7 +394,7 @@ public class AsYouTypeFormatter { return nextChar; } - private String inputDigitHelper(char nextChar) { + private String inputDigitHelper(char nextChar, boolean rememberPosition) { if (!PhoneNumberUtil.DIGIT_MAPPINGS.containsKey(nextChar)) { return currentOutput.toString(); } @@ -408,10 +410,7 @@ public class AsYouTypeFormatter { } else { // More digits are entered than we could handle. currentOutput.append(nextChar); ableToFormat = false; - if (positionRemembered > 0) { - positionRemembered = originalPosition; - currentOutput.setLength(0); - } + resetPositionOnFailureToFormat(); return accruedInput.toString(); } } diff --git a/java/src/com/google/i18n/phonenumbers/PhoneNumberMetadataProto b/java/src/com/google/i18n/phonenumbers/PhoneNumberMetadataProto index 31a15e565581a1a70e33aee54e942f1427f66dea..c2906bc21e22c117839528aabb6ed505bdcb70c7 100644 GIT binary patch delta 13333 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 delta 7025 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 countryCodeToRegionCodeMap = - new HashMap(310); + private final Map > countryCodeToRegionCodeMap = + new HashMap >(310); // The set of countries that share country code 1. There are roughly 26 countries of them and we // set the initial capacity of the HashSet to 35 to offer a load factor of roughly 0.75. private final HashSet nanpaCountries = new HashSet(35); private static final int NANPA_COUNTRY_CODE = 1; - // The set of countries that share country code 7. - private final HashSet russiaFederationCountries = new HashSet(3); - private static final int RUSSIAN_FED_COUNTRY_CODE = 7; - - // The set of countries that share country code 44. - private final HashSet greatBritainAndDependencies = new HashSet(7); - private static final int GREAT_BRITAIN_COUNTRY_CODE = 44; - - // The set of countries that share country code 262. - private final HashSet frenchIndianOceanTerritories = new HashSet(6); - - private static final int FRENCH_INDIAN_OCEAN_COUNTRY_CODE = 262; - // The PLUS_SIGN signifies the international prefix. static final char PLUS_SIGN = '+'; @@ -173,7 +160,6 @@ public class PhoneNumberUtil { aSet.add(225); // Cote d'Ivoire aSet.add(227); // Niger aSet.add(228); // Togo - aSet.add(240); // Equatorial Guinea aSet.add(241); // Gabon aSet.add(379); // Vatican City LEADING_ZERO_COUNTRIES = Collections.unmodifiableSet(aSet); @@ -214,7 +200,7 @@ public class PhoneNumberUtil { // not include other punctuation, as this will be stripped later during parsing and is of no // information value when parsing a number. private static final String VALID_START_CHAR = "[" + PLUS_CHARS + VALID_DIGITS + "]"; - private static final Pattern VALID_START_CHAR_PATTERN = Pattern.compile(VALID_START_CHAR); + static final Pattern VALID_START_CHAR_PATTERN = Pattern.compile(VALID_START_CHAR); // Regular expression of characters typically used to start a second phone number for the purposes // of parsing. This allows us to strip off parts of the number that are actually the start of @@ -260,8 +246,8 @@ public class PhoneNumberUtil { // Note that the only capturing groups should be around the digits that you want to capture as // part of the extension, or else parsing will fail! private static final String KNOWN_EXTN_PATTERNS = "[ \u00A0\\t,]*(?:ext(?:ensio)?n?|" + - "\uFF45\uFF58\uFF54\uFF4E?|[,x\uFF58#\uFF03~\uFF5E]|int|\uFF49\uFF4E\uFF54)" + - "[:\\.\uFF0E]?[ \u00A0\\t,]*([" + VALID_DIGITS + "]{1,7})|[- ]+([" + VALID_DIGITS + + "\uFF45\uFF58\uFF54\uFF4E?|[,x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)" + + "[:\\.\uFF0E]?[ \u00A0\\t,-]*([" + VALID_DIGITS + "]{1,7})|[- ]+([" + VALID_DIGITS + "]{1,5})#"; // Regexp of all known extension prefixes used by different countries followed by 1 or more valid @@ -276,10 +262,12 @@ public class PhoneNumberUtil { Pattern.compile(VALID_PHONE_NUMBER + "(?:" + KNOWN_EXTN_PATTERNS + ")?", Pattern.UNICODE_CASE | Pattern.CASE_INSENSITIVE); - private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("(\\D+)"); + private static final Pattern NON_DIGITS_PATTERN = Pattern.compile("(\\D+)"); private static final Pattern FIRST_GROUP_PATTERN = Pattern.compile("(\\$1)"); private static final Pattern NP_PATTERN = Pattern.compile("\\$NP"); private static final Pattern FG_PATTERN = Pattern.compile("\\$FG"); + private static final Pattern CC_PATTERN = Pattern.compile("\\$CC"); + private static final Pattern NON_DIGIT_PATTERN = Pattern.compile("\\D"); private static PhoneNumberUtil instance = null; @@ -287,10 +275,10 @@ public class PhoneNumberUtil { private HashMap countryToMetadataMap = new HashMap(); - // A cache for frequently used regular expressions. As most people use phone numbers primarily - // from one country, and there are roughly 30 regular expressions needed, the initial capacity of - // 50 offers a rough load factor of 0.75. - private RegexCache regexCache = new RegexCache(50); + // A cache for frequently used country-specific regular expressions. As most people use phone + // numbers primarily from one to two countries, and there are roughly 60 regular expressions + // needed, the initial capacity of 100 offers a rough load factor of 0.75. + private RegexCache regexCache = new RegexCache(100); /** * INTERNATIONAL and NATIONAL formats are consistent with the definition in ITU-T Recommendation @@ -371,34 +359,23 @@ public class PhoneNumberUtil { countryToMetadataMap.put(regionCode, metadata); countryToMetadataMap.put(regionCode.toLowerCase(), metadata); int countryCode = metadata.getCountryCode(); - switch (countryCode) { - case NANPA_COUNTRY_CODE: - nanpaCountries.add(regionCode); - nanpaCountries.add(regionCode.toLowerCase()); - break; - case RUSSIAN_FED_COUNTRY_CODE: - russiaFederationCountries.add(regionCode); - russiaFederationCountries.add(regionCode.toLowerCase()); - break; - case FRENCH_INDIAN_OCEAN_COUNTRY_CODE: - frenchIndianOceanTerritories.add(regionCode); - frenchIndianOceanTerritories.add(regionCode.toLowerCase()); - break; - case GREAT_BRITAIN_COUNTRY_CODE: - greatBritainAndDependencies.add(regionCode); - break; - default: - countryCodeToRegionCodeMap.put(countryCode, regionCode); - break; + if (countryCodeToRegionCodeMap.containsKey(countryCode)) { + if (metadata.getMainCountryForCode()) { + countryCodeToRegionCodeMap.get(countryCode).add(0, regionCode); + } else { + countryCodeToRegionCodeMap.get(countryCode).add(regionCode); + } + } else { + // For most countries, there will be only one region code for the country dialing code. + List listWithRegionCode = new ArrayList(1); + listWithRegionCode.add(regionCode); + countryCodeToRegionCodeMap.put(countryCode, listWithRegionCode); + } + if (countryCode == NANPA_COUNTRY_CODE) { + nanpaCountries.add(regionCode); + nanpaCountries.add(regionCode.toLowerCase()); } } - - // Override the value, so that 1 is always mapped to US, 7 is always mapped to RU, 44 to GB - // and 262 to RE. - countryCodeToRegionCodeMap.put(NANPA_COUNTRY_CODE, "US"); - countryCodeToRegionCodeMap.put(RUSSIAN_FED_COUNTRY_CODE, "RU"); - countryCodeToRegionCodeMap.put(FRENCH_INDIAN_OCEAN_COUNTRY_CODE, "RE"); - countryCodeToRegionCodeMap.put(GREAT_BRITAIN_COUNTRY_CODE, "GB"); } catch (IOException e) { LOGGER.log(Level.WARNING, e.toString()); } catch (ClassNotFoundException e) { @@ -422,14 +399,14 @@ public class PhoneNumberUtil { * found in the number */ static String extractPossibleNumber(String number) { - // Remove trailing non-alpha non-numerical characters. - Matcher trailingCharsMatcher = UNWANTED_END_CHAR_PATTERN.matcher(number); - if (trailingCharsMatcher.find()) { - number = number.substring(0, trailingCharsMatcher.start()); - } Matcher m = VALID_START_CHAR_PATTERN.matcher(number); if (m.find()) { number = number.substring(m.start()); + // Remove trailing non-alpha non-numerical characters. + Matcher trailingCharsMatcher = UNWANTED_END_CHAR_PATTERN.matcher(number); + if (trailingCharsMatcher.find()) { + number = number.substring(0, trailingCharsMatcher.start()); + } // Check for extra numbers at the end. Matcher secondNumber = SECOND_NUMBER_START_PATTERN.matcher(number); if (secondNumber.find()) { @@ -710,9 +687,9 @@ public Set getSupportedCountries() { formatNumberByFormat(countryCode, PhoneNumberFormat.E164, formattedNumber); return; } - // Note here that all NANPA formatting rules are contained by US, so we use that to format NANPA - // numbers. The same applies to Russian Fed countries - rules are contained by Russia. French - // Indian Ocean country rules are contained by Reunion. + // Note getRegionCodeForCountryCode() is used because formatting information for countries which + // share a country code is contained by only one country for performance reasons. For example, + // for NANPA countries it will be contained in the metadata for US. String regionCode = getRegionCodeForCountryCode(countryCode); if (!isValidRegionCode(regionCode)) { formattedNumber.append(nationalSignificantNumber); @@ -740,11 +717,11 @@ public Set getSupportedCountries() { PhoneNumberFormat numberFormat, List userDefinedFormats) { int countryCode = number.getCountryCode(); + String nationalSignificantNumber = getNationalSignificantNumber(number); // Note getRegionCodeForCountryCode() is used because formatting information for countries which // share a country code is contained by only one country for performance reasons. For example, // for NANPA countries it will be contained in the metadata for US. String regionCode = getRegionCodeForCountryCode(countryCode); - String nationalSignificantNumber = getNationalSignificantNumber(number); if (!isValidRegionCode(regionCode)) { return nationalSignificantNumber; } @@ -772,6 +749,27 @@ public Set getSupportedCountries() { return formattedNumber.toString(); } + public String formatNationalNumberWithCarrierCode(PhoneNumber number, String carrierCode) { + int countryCode = number.getCountryCode(); + String nationalSignificantNumber = getNationalSignificantNumber(number); + // Note getRegionCodeForCountryCode() is used because formatting information for countries which + // share a country code is contained by only one country for performance reasons. For example, + // for NANPA countries it will be contained in the metadata for US. + String regionCode = getRegionCodeForCountryCode(countryCode); + if (!isValidRegionCode(regionCode)) { + return nationalSignificantNumber; + } + + StringBuffer formattedNumber = new StringBuffer(20); + formattedNumber.append(formatNationalNumber(nationalSignificantNumber, + regionCode, + PhoneNumberFormat.NATIONAL, + carrierCode)); + maybeGetFormattedExtension(number, regionCode, formattedNumber); + formatNumberByFormat(countryCode, PhoneNumberFormat.NATIONAL, formattedNumber); + return formattedNumber.toString(); + } + /** * Formats a phone number for out-of-country dialing purpose. If no countryCallingFrom * is supplied, we format the number in its INTERNATIONAL format. If the countryCallingFrom is @@ -796,55 +794,24 @@ public Set getSupportedCountries() { return format(number, PhoneNumberFormat.INTERNATIONAL); } int countryCode = number.getCountryCode(); - if (countryCode == NANPA_COUNTRY_CODE && isNANPACountry(countryCallingFrom)) { - // For NANPA countries, return the national format for these countries but prefix it with the - // country code. - return countryCode + " " + format(number, PhoneNumberFormat.NATIONAL); - } - if (countryCode == FRENCH_INDIAN_OCEAN_COUNTRY_CODE && - frenchIndianOceanTerritories.contains(countryCallingFrom)) { - // For dialling between FRENCH_INDIAN_OCEAN countries, the 10 digit number is all we need. - // Technically this is the case for dialling from la Reunion to other overseas departments of - // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this - // edge case for now and for those cases return the version including country code. - // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion - return format(number, PhoneNumberFormat.NATIONAL); - } - if (countryCode == GREAT_BRITAIN_COUNTRY_CODE && - greatBritainAndDependencies.contains(countryCallingFrom)) { - // It seems that numbers can be dialled in national format between Great Britain and the crown - // dependencies with the same country code. - return format(number, PhoneNumberFormat.NATIONAL); - } - // If the country code is the Russian Fed country code, we check the number itself to determine - // which region code it is for. We don't do this for NANPA countries because of performance - // reasons, and instead use US rules for all NANPA numbers. Also, NANPA countries share the - // same national and international prefixes, which is not the case for Russian Fed countries. - // There is also a special case for toll-free and premium rate numbers dialled within Russian - // Fed countries. - String regionCode; - if (countryCode == RUSSIAN_FED_COUNTRY_CODE) { - if (russiaFederationCountries.contains(countryCallingFrom)) { - // For toll-free numbers and premium rate numbers dialled from within Russian Fed countries, - // we should format them as if they are local numbers. - // A toll-free number would be dialled from KZ as 8-800-080-7777 but from Russia as - // 0-800-080-7777. (Confirmation on government websites such as e.gov.kz). - PhoneNumberType numberType = getNumberType(number); - if (numberType == PhoneNumberType.TOLL_FREE || numberType == PhoneNumberType.PREMIUM_RATE) { - return format(number, PhoneNumberFormat.NATIONAL); - } - } - // Otherwise, we should find out what region the number really belongs to before continuing, - // since they have different formatting rules. - regionCode = getRegionCodeForNumber(number); - } else { - regionCode = getRegionCodeForCountryCode(countryCode); - } + String regionCode = getRegionCodeForCountryCode(countryCode); String nationalSignificantNumber = getNationalSignificantNumber(number); if (!isValidRegionCode(regionCode)) { return nationalSignificantNumber; } - if (regionCode.equalsIgnoreCase(countryCallingFrom)) { + if (countryCode == NANPA_COUNTRY_CODE) { + if (isNANPACountry(countryCallingFrom)) { + // For NANPA countries, return the national format for these countries but prefix it with + // the country code. + return countryCode + " " + format(number, PhoneNumberFormat.NATIONAL); + } + } else if (countryCode == getCountryCodeForRegion(countryCallingFrom)) { + // For countries that share a country calling code, the country code need not be dialled. This + // also applies when dialling within a country, so this if clause covers both these cases. + // Technically this is the case for dialling from la RŽunion to other overseas departments of + // France (French Guiana, Martinique, Guadeloupe), but not vice versa - so we don't cover this + // edge case for now and for those cases return the version including country code. + // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion return format(number, PhoneNumberFormat.NATIONAL); } String formattedNationalNumber = @@ -881,26 +848,25 @@ public Set getSupportedCountries() { * passed in. If such information is missing, the number will be formatted into the NATIONAL * format by default. * - * @param number The PhoneNumber that needs to be formatted in its original number format - * @param defaultCountry the country whose IDD needs to be appended if the original number has - * one - * @return The formatted phone number in its original number format + * @param number the PhoneNumber that needs to be formatted in its original number format + * @param countryCallingFrom the country whose IDD needs to be prefixed if the original number + * has one + * @return the formatted phone number in its original number format */ - public String formatUsingOriginalNumberFormat(PhoneNumber number, String defaultCountry) { - if (!number.hasRawInput()) { + public String formatInOriginalFormat(PhoneNumber number, String countryCallingFrom) { + if (!number.hasCountryCodeSource()) { return format(number, PhoneNumberFormat.NATIONAL); } switch (number.getCountryCodeSource()) { - case FROM_DEFAULT_COUNTRY: - return format(number, PhoneNumberFormat.NATIONAL); case FROM_NUMBER_WITH_PLUS_SIGN: return format(number, PhoneNumberFormat.INTERNATIONAL); case FROM_NUMBER_WITH_IDD: - return formatOutOfCountryCallingNumber(number, defaultCountry); + return formatOutOfCountryCallingNumber(number, countryCallingFrom); case FROM_NUMBER_WITHOUT_PLUS_SIGN: return format(number, PhoneNumberFormat.INTERNATIONAL).substring(1); + case FROM_DEFAULT_COUNTRY: default: - return number.getRawInput(); + return format(number, PhoneNumberFormat.NATIONAL); } } @@ -947,12 +913,21 @@ public Set getSupportedCountries() { } } + // Simple wrapper of formatNationalNumber for the common case of no carrier code. + private String formatNationalNumber(String number, + String regionCode, + PhoneNumberFormat numberFormat) { + return formatNationalNumber(number, regionCode, numberFormat, null); + } + // Note in some countries, the national number can be written in two completely different ways // depending on whether it forms part of the NATIONAL format or INTERNATIONAL format. The - // numberFormat parameter here is used to specify which format to use for those cases. + // numberFormat parameter here is used to specify which format to use for those cases. If a + // carrierCode is specified, this will be inserted into the formatted string to replace $CC. private String formatNationalNumber(String number, String regionCode, - PhoneNumberFormat numberFormat) { + PhoneNumberFormat numberFormat, + String carrierCode) { PhoneMetadata metadata = getMetadataForRegion(regionCode); List intlNumberFormats = metadata.getIntlNumberFormatList(); // When the intlNumberFormats exists, we use that to format national number for the @@ -961,27 +936,48 @@ public Set getSupportedCountries() { (intlNumberFormats.size() == 0 || numberFormat == PhoneNumberFormat.NATIONAL) ? metadata.getNumberFormatList() : metadata.getIntlNumberFormatList(); - return formatAccordingToFormats(number, availableFormats, numberFormat); + return formatAccordingToFormats(number, availableFormats, numberFormat, carrierCode); } + // Simple wrapper of formatAccordingToFormats for the common case of no carrier code. private String formatAccordingToFormats(String nationalNumber, List availableFormats, PhoneNumberFormat numberFormat) { + return formatAccordingToFormats(nationalNumber, availableFormats, numberFormat, null); + } + + // Note that carrierCode is optional - if NULL or an empty string, no carrier code replacement + // will take place. Carrier code replacement occurs before national prefix replacement. + private String formatAccordingToFormats(String nationalNumber, + List availableFormats, + PhoneNumberFormat numberFormat, + String carrierCode) { for (NumberFormat numFormat : availableFormats) { if (!numFormat.hasLeadingDigits() || regexCache.getPatternForRegex(numFormat.getLeadingDigits()).matcher(nationalNumber) .lookingAt()) { - Pattern patternToMatch = regexCache.getPatternForRegex(numFormat.getPattern()); - Matcher m = patternToMatch.matcher(nationalNumber); + Matcher m = regexCache.getPatternForRegex(numFormat.getPattern()).matcher(nationalNumber); + String numberFormatRule = numFormat.getFormat(); if (m.matches()) { + if (carrierCode != null && carrierCode.length() > 0 && + numFormat.getDomesticCarrierCodeFormattingRule().length() > 0) { + // Replace the $CC in the formatting rule with the desired carrier code. + String carrierCodeFormattingRule = numFormat.getDomesticCarrierCodeFormattingRule(); + carrierCodeFormattingRule = + CC_PATTERN.matcher(carrierCodeFormattingRule).replaceFirst(carrierCode); + // Now replace the $FG in the formatting rule with the first group and the carrier code + // combined in the appropriate way. + numberFormatRule = FIRST_GROUP_PATTERN.matcher(numberFormatRule) + .replaceFirst(carrierCodeFormattingRule); + } String nationalPrefixFormattingRule = numFormat.getNationalPrefixFormattingRule(); - if (nationalPrefixFormattingRule != null && nationalPrefixFormattingRule.length() > 0 && - numberFormat == PhoneNumberFormat.NATIONAL) { - Matcher firstGroupMatcher = - FIRST_GROUP_PATTERN.matcher(numFormat.getFormat()); + if (numberFormat == PhoneNumberFormat.NATIONAL && + nationalPrefixFormattingRule != null && + nationalPrefixFormattingRule.length() > 0) { + Matcher firstGroupMatcher = FIRST_GROUP_PATTERN.matcher(numberFormatRule); return m.replaceAll(firstGroupMatcher.replaceFirst(nationalPrefixFormattingRule)); } else { - return m.replaceAll(numFormat.getFormat()); + return m.replaceAll(numberFormatRule); } } } @@ -1150,7 +1146,6 @@ public Set getSupportedCountries() { Matcher nationalNumberPatternMatcher = regexCache.getPatternForRegex(numberDesc.getNationalNumberPattern()) .matcher(nationalNumber); - return possibleNumberPatternMatcher.matches() && nationalNumberPatternMatcher.matches(); } @@ -1203,39 +1198,34 @@ public Set getSupportedCountries() { * the country/region level. * * @param number the phone number whose origin we want to know - * @return the country/region where the phone number is from + * @return the country/region where the phone number is from, or null if no country matches this + * calling code. */ public String getRegionCodeForNumber(PhoneNumber number) { int countryCode = number.getCountryCode(); - switch (countryCode) { - case NANPA_COUNTRY_CODE: - // Override this and try the US case first, since it is more likely than other countries, - // for performance reasons. - String nationalNumber = getNationalSignificantNumber(number); - if (getNumberTypeHelper(nationalNumber, - getMetadataForRegion("US")) != PhoneNumberType.UNKNOWN) { - return "US"; - } - HashSet nanpaExceptUS = new HashSet(nanpaCountries); - nanpaExceptUS.remove("US"); - return getRegionCodeForNumberFromRegionList(number, nanpaExceptUS); - case RUSSIAN_FED_COUNTRY_CODE: - return getRegionCodeForNumberFromRegionList(number, russiaFederationCountries); - case FRENCH_INDIAN_OCEAN_COUNTRY_CODE: - return getRegionCodeForNumberFromRegionList(number, frenchIndianOceanTerritories); - case GREAT_BRITAIN_COUNTRY_CODE: - return getRegionCodeForNumberFromRegionList(number, greatBritainAndDependencies); - default: - return getRegionCodeForCountryCode(countryCode); + List regions = countryCodeToRegionCodeMap.get(countryCode); + if (regions == null) { + return null; + } + if (regions.size() == 1) { + return regions.get(0); + } else { + return getRegionCodeForNumberFromRegionList(number, regions); } } private String getRegionCodeForNumberFromRegionList(PhoneNumber number, - HashSet regionCodes) { + List regionCodes) { String nationalNumber = String.valueOf(number.getNationalNumber()); for (String regionCode : regionCodes) { - if (getNumberTypeHelper(nationalNumber, getMetadataForRegion(regionCode)) != - PhoneNumberType.UNKNOWN) { + // If leadingDigits is present, use this. Otherwise, do full validation. + PhoneMetadata metadata = getMetadataForRegion(regionCode); + if (metadata.hasLeadingDigits()) { + if (regexCache.getPatternForRegex(metadata.getLeadingDigits()) + .matcher(nationalNumber).lookingAt()) { + return regionCode; + } + } else if (getNumberTypeHelper(nationalNumber, metadata) != PhoneNumberType.UNKNOWN) { return regionCode; } } @@ -1244,11 +1234,12 @@ public Set getSupportedCountries() { /** * Returns the region code that matches the specific country code. In the case of no region code - * being found, ZZ will be returned. + * being found, ZZ will be returned. In the case of multiple regions, the one designated in the + * metadata as the "main" country for this calling code will be returned. */ - String getRegionCodeForCountryCode(int countryCode) { - String regionCode = countryCodeToRegionCodeMap.get(countryCode); - return regionCode == null ? "ZZ" : regionCode; + public String getRegionCodeForCountryCode(int countryCode) { + List regionCodes = countryCodeToRegionCodeMap.get(countryCode); + return regionCodes == null ? "ZZ" : regionCodes.get(0); } /** @@ -1330,6 +1321,18 @@ public Set getSupportedCountries() { } String nationalNumber = getNationalSignificantNumber(number); PhoneNumberDesc generalNumDesc = getMetadataForRegion(regionCode).getGeneralDesc(); + // Handling case of numbers with no metadata. + if (!generalNumDesc.hasNationalNumberPattern()) { + LOGGER.log(Level.FINER, "Checking if number is possible with incomplete metadata."); + int numberLength = nationalNumber.length(); + if (numberLength < MIN_LENGTH_FOR_NSN) { + return ValidationResult.TOO_SHORT; + } else if (numberLength > MAX_LENGTH_FOR_NSN) { + return ValidationResult.TOO_LONG; + } else { + return ValidationResult.IS_POSSIBLE; + } + } String possibleNumberPattern = generalNumDesc.getPossibleNumberPattern(); Matcher m = regexCache.getPatternForRegex(possibleNumberPattern).matcher(nationalNumber); if (m.lookingAt()) { @@ -1622,8 +1625,7 @@ public Set getSupportedCountries() { // it is an extension. if (m.find() && isViablePhoneNumber(number.substring(0, m.start()))) { // The numbers are captured into groups in the regular expression. - int length = m.groupCount(); - for (int i = 1; i <= length; i++) { + for (int i = 1, length = m.groupCount(); i <= length; i++) { if (m.group(i) != null) { // We go through the capturing groups until we find one that captured some digits. If none // did, then we will return the empty string. @@ -1665,8 +1667,10 @@ public Set getSupportedCountries() { public void parse(String numberToParse, String defaultCountry, PhoneNumber phoneNumber) throws NumberParseException { if (!isValidRegionCode(defaultCountry)) { - throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE, - "No default country was supplied."); + if (numberToParse.charAt(0) != PLUS_SIGN) { + throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE, + "Missing or invalid default country."); + } } parseHelper(numberToParse, defaultCountry, false, phoneNumber); } @@ -1700,8 +1704,10 @@ public Set getSupportedCountries() { PhoneNumber phoneNumber) throws NumberParseException { if (!isValidRegionCode(defaultCountry)) { - throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE, - "No default country was supplied."); + if (numberToParse.charAt(0) != PLUS_SIGN) { + throw new NumberParseException(NumberParseException.ErrorType.INVALID_COUNTRY_CODE, + "Missing or invalid default country."); + } } parseHelper(numberToParse, defaultCountry, true, phoneNumber); } diff --git a/java/src/com/google/i18n/phonenumbers/Phonemetadata.java b/java/src/com/google/i18n/phonenumbers/Phonemetadata.java index f7715c126..2e35d2259 100644 --- a/java/src/com/google/i18n/phonenumbers/Phonemetadata.java +++ b/java/src/com/google/i18n/phonenumbers/Phonemetadata.java @@ -77,6 +77,19 @@ public final class Phonemetadata { return this; } + // optional string domestic_carrier_code_formatting_rule = 5; + private boolean hasDomesticCarrierCodeFormattingRule; + private String domesticCarrierCodeFormattingRule_ = ""; + public boolean hasDomesticCarrierCodeFormattingRule() { + return hasDomesticCarrierCodeFormattingRule; } + public String getDomesticCarrierCodeFormattingRule() { + return domesticCarrierCodeFormattingRule_; } + public NumberFormat setDomesticCarrierCodeFormattingRule(String value) { + hasDomesticCarrierCodeFormattingRule = true; + domesticCarrierCodeFormattingRule_ = value; + return this; + } + public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeUTF(pattern_); objectOutput.writeUTF(format_); @@ -88,6 +101,10 @@ public final class Phonemetadata { if (hasNationalPrefixFormattingRule) { objectOutput.writeUTF(nationalPrefixFormattingRule_); } + objectOutput.writeBoolean(hasDomesticCarrierCodeFormattingRule); + if (hasDomesticCarrierCodeFormattingRule) { + objectOutput.writeUTF(domesticCarrierCodeFormattingRule_); + } } public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException { @@ -99,6 +116,9 @@ public final class Phonemetadata { if (objectInput.readBoolean()) { setNationalPrefixFormattingRule(objectInput.readUTF()); } + if (objectInput.readBoolean()) { + setDomesticCarrierCodeFormattingRule(objectInput.readUTF()); + } } } @@ -441,6 +461,28 @@ public final class Phonemetadata { return this; } + // optional bool main_country_for_code = 22 [default = false]; + private boolean hasMainCountryForCode; + private boolean mainCountryForCode_ = false; + public boolean hasMainCountryForCode() { return hasMainCountryForCode; } + public boolean getMainCountryForCode() { return mainCountryForCode_; } + public PhoneMetadata setMainCountryForCode(boolean value) { + hasMainCountryForCode = true; + mainCountryForCode_ = value; + return this; + } + + // optional string leading_digits = 23; + private boolean hasLeadingDigits; + private String leadingDigits_ = ""; + public boolean hasLeadingDigits() { return hasLeadingDigits; } + public String getLeadingDigits() { return leadingDigits_; } + public PhoneMetadata setLeadingDigits(String value) { + hasLeadingDigits = true; + leadingDigits_ = value; + return this; + } + public void writeExternal(ObjectOutput objectOutput) throws IOException { objectOutput.writeBoolean(hasGeneralDesc); if (hasGeneralDesc) { @@ -517,6 +559,13 @@ public final class Phonemetadata { for (int i = 0; i < intlNumberFormatSize; i++) { intlNumberFormat_.get(i).writeExternal(objectOutput); } + + objectOutput.writeBoolean(mainCountryForCode_); + + objectOutput.writeBoolean(hasLeadingDigits); + if (hasLeadingDigits) { + objectOutput.writeUTF(leadingDigits_); + } } public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException { @@ -613,6 +662,13 @@ public final class Phonemetadata { numFormat.readExternal(objectInput); intlNumberFormat_.add(numFormat); } + + setMainCountryForCode(objectInput.readBoolean()); + + hasString = objectInput.readBoolean(); + if (hasString) { + setLeadingDigits(objectInput.readUTF()); + } } } diff --git a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java index 087e52e46..9b294077a 100644 --- a/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java +++ b/java/test/com/google/i18n/phonenumbers/AsYouTypeFormatterTest.java @@ -67,6 +67,97 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals("1 650 253 2222", formatter.inputDigit('2')); formatter.clear(); + assertEquals("0", formatter.inputDigit('0')); + assertEquals("01", formatter.inputDigit('1')); + assertEquals("011", formatter.inputDigit('1')); + assertEquals("0114", formatter.inputDigit('4')); + assertEquals("01144", formatter.inputDigit('4')); + assertEquals("011 44 6", formatter.inputDigit('6')); + assertEquals("011 44 61", formatter.inputDigit('1')); + assertEquals("011 44 612", formatter.inputDigit('2')); + assertEquals("011 44 6 123", formatter.inputDigit('3')); + assertEquals("011 44 6 123 1", formatter.inputDigit('1')); + assertEquals("011 44 6 123 12", formatter.inputDigit('2')); + assertEquals("011 44 6 123 123", formatter.inputDigit('3')); + assertEquals("011 44 6 123 123 1", formatter.inputDigit('1')); + assertEquals("011 44 6 123 123 12", formatter.inputDigit('2')); + assertEquals("011 44 6 123 123 123", formatter.inputDigit('3')); + + formatter.clear(); + assertEquals("0", formatter.inputDigit('0')); + assertEquals("01", formatter.inputDigit('1')); + assertEquals("011", formatter.inputDigit('1')); + assertEquals("0115", formatter.inputDigit('5')); + assertEquals("01154", formatter.inputDigit('4')); + assertEquals("011 54 9", formatter.inputDigit('9')); + assertEquals("011 54 91", formatter.inputDigit('1')); + assertEquals("011 54 911", formatter.inputDigit('1')); + assertEquals("011 54 9 11 2", + formatter.inputDigit('2')); + assertEquals("011 54 9 11 23", formatter.inputDigit('3')); + assertEquals("011 54 9 11 231", formatter.inputDigit('1')); + assertEquals("011 54 9 11 2312", formatter.inputDigit('2')); + assertEquals("011 54 9 11 2312 1", formatter.inputDigit('1')); + assertEquals("011 54 9 11 2312 12", formatter.inputDigit('2')); + assertEquals("011 54 9 11 2312 123", formatter.inputDigit('3')); + assertEquals("011 54 9 11 2312 1234", formatter.inputDigit('4')); + + formatter.clear(); + assertEquals("+", formatter.inputDigit('+')); + assertEquals("+4", formatter.inputDigit('4')); + assertEquals("+48", formatter.inputDigit('8')); + assertEquals("+488", formatter.inputDigit('8')); + assertEquals("+4888", formatter.inputDigit('8')); + assertEquals("+48 881", formatter.inputDigit('1')); + assertEquals("+48 88 12", formatter.inputDigit('2')); + assertEquals("+48 88 123", formatter.inputDigit('3')); + assertEquals("+48 88 123 1", formatter.inputDigit('1')); + assertEquals("+48 88 123 12", formatter.inputDigit('2')); + assertEquals("+48 88 123 12 1", formatter.inputDigit('1')); + assertEquals("+48 88 123 12 12", formatter.inputDigit('2')); + } + + public void testAsYouTypeFormatterUSFullWidthCharacters() { + AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("US"); + assertEquals("\uFF16", formatter.inputDigit('\uFF16')); + assertEquals("\uFF16\uFF15", formatter.inputDigit('\uFF15')); + assertEquals("\uFF16\uFF15\uFF10", formatter.inputDigit('\uFF10')); + assertEquals("\uFF16\uFF15\uFF10\uFF12", formatter.inputDigit('\uFF12')); + assertEquals("\uFF16\uFF15\uFF10\uFF12\uFF15", formatter.inputDigit('\uFF15')); + assertEquals("650 253", formatter.inputDigit('\uFF13')); + assertEquals("650 253 2", formatter.inputDigit('\uFF12')); + assertEquals("650 253 22", formatter.inputDigit('\uFF12')); + assertEquals("650 253 222", formatter.inputDigit('\uFF12')); + assertEquals("650 253 2222", formatter.inputDigit('\uFF12')); + } + + public void testAsYouTypeFormatterUSMobileShortCode() { + AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("US"); + assertEquals("*", formatter.inputDigit('*')); + assertEquals("*1", formatter.inputDigit('1')); + assertEquals("*12", formatter.inputDigit('2')); + assertEquals("*121", formatter.inputDigit('1')); + assertEquals("*121#", formatter.inputDigit('#')); + } + + public void testAsYouTypeFormatterUSVanityNumber() { + AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("US"); + assertEquals("8", formatter.inputDigit('8')); + assertEquals("80", formatter.inputDigit('0')); + assertEquals("800", formatter.inputDigit('0')); + assertEquals("800 ", formatter.inputDigit(' ')); + assertEquals("800 M", formatter.inputDigit('M')); + assertEquals("800 MY", formatter.inputDigit('Y')); + assertEquals("800 MY ", formatter.inputDigit(' ')); + assertEquals("800 MY A", formatter.inputDigit('A')); + assertEquals("800 MY AP", formatter.inputDigit('P')); + assertEquals("800 MY APP", formatter.inputDigit('P')); + assertEquals("800 MY APPL", formatter.inputDigit('L')); + assertEquals("800 MY APPLE", formatter.inputDigit('E')); + } + + public void testAsYouTypeFormatterAndRememberPositionUS() { + AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("US"); assertEquals("1", formatter.inputDigitAndRememberPosition('1')); assertEquals(1, formatter.getRememberedPosition()); assertEquals("16", formatter.inputDigit('6')); @@ -76,6 +167,8 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals(4, formatter.getRememberedPosition()); assertEquals("16502", formatter.inputDigit('2')); assertEquals("1 650 25", formatter.inputDigit('5')); + // Note the remembered position for digit "0" changes from 4 to 5, because a space is now + // inserted in the front. assertEquals(5, formatter.getRememberedPosition()); assertEquals("1 650 253", formatter.inputDigit('3')); assertEquals("1 650 253 2", formatter.inputDigit('2')); @@ -90,26 +183,6 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals("1650253222222", formatter.inputDigit('2')); assertEquals(10, formatter.getRememberedPosition()); - formatter.clear(); - assertEquals("1", formatter.inputDigit('1')); - assertEquals("16", formatter.inputDigit('6')); - assertEquals("165", formatter.inputDigit('5')); - assertEquals("1650", formatter.inputDigitAndRememberPosition('0')); - assertEquals(4, formatter.getRememberedPosition()); - assertEquals("16502", formatter.inputDigit('2')); - assertEquals("1 650 25", formatter.inputDigit('5')); - assertEquals(5, formatter.getRememberedPosition()); - assertEquals("1 650 253", formatter.inputDigit('3')); - assertEquals("1 650 253 2", formatter.inputDigit('2')); - assertEquals("1 650 253 22", formatter.inputDigit('2')); - assertEquals(5, formatter.getRememberedPosition()); - assertEquals("1 650 253 222", formatter.inputDigit('2')); - assertEquals("1 650 253 2222", formatter.inputDigit('2')); - assertEquals("165025322222", formatter.inputDigit('2')); - assertEquals(4, formatter.getRememberedPosition()); - assertEquals("1650253222222", formatter.inputDigit('2')); - assertEquals(4, formatter.getRememberedPosition()); - formatter.clear(); assertEquals("1", formatter.inputDigit('1')); assertEquals("16", formatter.inputDigit('6')); @@ -184,41 +257,6 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals("011 48 88 123 12 1", formatter.inputDigit('1')); assertEquals("011 48 88 123 12 12", formatter.inputDigit('2')); - formatter.clear(); - assertEquals("0", formatter.inputDigit('0')); - assertEquals("01", formatter.inputDigit('1')); - assertEquals("011", formatter.inputDigit('1')); - assertEquals("0114", formatter.inputDigit('4')); - assertEquals("01144", formatter.inputDigit('4')); - assertEquals("011 44 6", formatter.inputDigit('6')); - assertEquals("011 44 61", formatter.inputDigit('1')); - assertEquals("011 44 612", formatter.inputDigit('2')); - assertEquals("011 44 6 123", formatter.inputDigit('3')); - assertEquals("011 44 6 123 1", formatter.inputDigit('1')); - assertEquals("011 44 6 123 12", formatter.inputDigit('2')); - assertEquals("011 44 6 123 123", formatter.inputDigit('3')); - assertEquals("011 44 6 123 123 1", formatter.inputDigit('1')); - assertEquals("011 44 6 123 123 12", formatter.inputDigit('2')); - assertEquals("011 44 6 123 123 123", formatter.inputDigit('3')); - - formatter.clear(); - assertEquals("0", formatter.inputDigit('0')); - assertEquals("01", formatter.inputDigit('1')); - assertEquals("011", formatter.inputDigit('1')); - assertEquals("0115", formatter.inputDigit('5')); - assertEquals("01154", formatter.inputDigit('4')); - assertEquals("011 54 9", formatter.inputDigit('9')); - assertEquals("011 54 91", formatter.inputDigit('1')); - assertEquals("011 54 911", formatter.inputDigit('1')); - assertEquals("011 54 9 11 2", formatter.inputDigit('2')); - assertEquals("011 54 9 11 23", formatter.inputDigit('3')); - assertEquals("011 54 9 11 231", formatter.inputDigit('1')); - assertEquals("011 54 9 11 2312", formatter.inputDigit('2')); - assertEquals("011 54 9 11 2312 1", formatter.inputDigit('1')); - assertEquals("011 54 9 11 2312 12", formatter.inputDigit('2')); - assertEquals("011 54 9 11 2312 123", formatter.inputDigit('3')); - assertEquals("011 54 9 11 2312 1234", formatter.inputDigit('4')); - formatter.clear(); assertEquals("+", formatter.inputDigit('+')); assertEquals("+1", formatter.inputDigit('1')); @@ -251,56 +289,6 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals("+1 650 253 222", formatter.inputDigit('2')); assertEquals("+1650253222;", formatter.inputDigit(';')); assertEquals(3, formatter.getRememberedPosition()); - - formatter.clear(); - assertEquals("+", formatter.inputDigit('+')); - assertEquals("+4", formatter.inputDigit('4')); - assertEquals("+48", formatter.inputDigit('8')); - assertEquals("+488", formatter.inputDigit('8')); - assertEquals("+4888", formatter.inputDigit('8')); - assertEquals("+48 881", formatter.inputDigit('1')); - assertEquals("+48 88 12", formatter.inputDigit('2')); - assertEquals("+48 88 123", formatter.inputDigit('3')); - assertEquals("+48 88 123 1", formatter.inputDigit('1')); - assertEquals("+48 88 123 12", formatter.inputDigit('2')); - assertEquals("+48 88 123 12 1", formatter.inputDigit('1')); - assertEquals("+48 88 123 12 12", formatter.inputDigit('2')); - - // Test US number with full-width characters. - formatter.clear(); - assertEquals("\uFF16", formatter.inputDigit('\uFF16')); - assertEquals("\uFF16\uFF15", formatter.inputDigit('\uFF15')); - assertEquals("\uFF16\uFF15\uFF10", formatter.inputDigit('\uFF10')); - assertEquals("\uFF16\uFF15\uFF10\uFF12", formatter.inputDigit('\uFF12')); - assertEquals("\uFF16\uFF15\uFF10\uFF12\uFF15", formatter.inputDigit('\uFF15')); - assertEquals("650 253", formatter.inputDigit('\uFF13')); - assertEquals("650 253 2", formatter.inputDigit('\uFF12')); - assertEquals("650 253 22", formatter.inputDigit('\uFF12')); - assertEquals("650 253 222", formatter.inputDigit('\uFF12')); - assertEquals("650 253 2222", formatter.inputDigit('\uFF12')); - - // Mobile short code. - formatter.clear(); - assertEquals("*", formatter.inputDigit('*')); - assertEquals("*1", formatter.inputDigit('1')); - assertEquals("*12", formatter.inputDigit('2')); - assertEquals("*121", formatter.inputDigit('1')); - assertEquals("*121#", formatter.inputDigit('#')); - - // Test vanity numbers. - formatter.clear(); - assertEquals("8", formatter.inputDigit('8')); - assertEquals("80", formatter.inputDigit('0')); - assertEquals("800", formatter.inputDigit('0')); - assertEquals("800 ", formatter.inputDigit(' ')); - assertEquals("800 M", formatter.inputDigit('M')); - assertEquals("800 MY", formatter.inputDigit('Y')); - assertEquals("800 MY ", formatter.inputDigit(' ')); - assertEquals("800 MY A", formatter.inputDigit('A')); - assertEquals("800 MY AP", formatter.inputDigit('P')); - assertEquals("800 MY APP", formatter.inputDigit('P')); - assertEquals("800 MY APPL", formatter.inputDigit('L')); - assertEquals("800 MY APPLE", formatter.inputDigit('E')); } public void testAsYouTypeFormatterGBFixedLine() { @@ -321,7 +309,7 @@ public class AsYouTypeFormatterTest extends TestCase { } public void testAsYouTypeFormatterGBTollFree() { - AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("gb"); + AsYouTypeFormatter formatter = phoneUtil.getAsYouTypeFormatter("gb"); assertEquals("0", formatter.inputDigit('0')); assertEquals("08", formatter.inputDigit('8')); assertEquals("080", formatter.inputDigit('0')); @@ -357,6 +345,7 @@ public class AsYouTypeFormatterTest extends TestCase { assertEquals("021", formatter.inputDigit('1')); assertEquals("0211", formatter.inputDigit('1')); assertEquals("02112", formatter.inputDigit('2')); + // Note the unittest is using fake metadata which might produce non-ideal results. assertEquals("02-112 3", formatter.inputDigit('3')); assertEquals("02-112 34", formatter.inputDigit('4')); assertEquals("02-112 345", formatter.inputDigit('5')); diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberMetadataProtoForTesting b/java/test/com/google/i18n/phonenumbers/PhoneNumberMetadataProtoForTesting index 5418da9c9ecce1bb59f49f575bc78b1cf0b52543..8b72ac93af352998c6cc399004013517e949b895 100644 GIT binary patch delta 1133 zcmZ{j%WD%+6o>Df$z(D~X7Wxdt$k=(GPY^wkxXWM*P<@8Xsbv|8f{C#RS`i!ZK5vS zX*qkLpe{r#B`S4g7X{b;4{ihnx4IC|y)%6&4MWb{^Z4C!zI*4RvFfOAeh}#aAt8dl z#IicSwwR*=CD0N~g%Z&ynv@V&r^8{iB=5mvp$|kc4Lw2_6T(Haw>UNiD^e$%7BZ}I zf)0v=D_Cs#Rv3z~(vFg+V{sln3mOV!U?tg&C8~tExWh^DmEQ+^75iaU>4TAw9A%+g zX=Y)`gzG{9l8OY=Ax&a#(!LqGK}CFRZ%TFk0zGn>IpCr^#O=Lc{Zbyx@gcV-@>T6^ z)n9Tf!Bv?u#kpn{+L=hfmp!6XU@l*P-cTZ~)aqurS}S`EN~99v(Q%Lg_@D$`!KU&D zK1ChA|9G}vqBiTOD|VQBPQ>m6W3I*Kp(B24XG9Ao)Gl}(Kf@Eh#|ODBsCGaq0*k7~ zeM_n@zot%cv}q&+Pvf1SYnOS#eeD>x@3bnn-?a1G8i`S#9o$az1bsY9Jb{DBDRxw= zDIecTauR+fkAt3?f`=)7imRz%Y~vC?SUv5r?78$c5n;jm^hO|cH}gVf5VNHjIF$AK zAI}=WmaXjXN4pV6>z3m-@NC@{qD1Sp82O0hSh#Suos=l(G9A;hD;Q5ZrBg?}x@9+- zm74x>3!Z?GietxP~9DVZ7*~l36#*M$^Los?%t?m8MrW$~HUS|Fm5* zC|))P!jot5o`m5`IFqHMue3Fb)zaB@wctvw*u6C^B^jKcxu@}_3ro4@44fE&1NjSh z!A96kii)mbU?D#3_53X5$TX&D`_h$`P9*l#Su7{mf--XZ0M#tVb8x5%V_n%r1F?Dz r9|?;ij_qN54|a5!#rWg2=4gNS6v03I#WAM7MPzF0@@(UsCnK<8BV;e&^g*^*2>x^&8`N8B>{j z#)5J6_02Tl3DvPi6?~^GK9Y%y;$y?!k0m3CrlNJle9<`sTj4Q92s|c%cS;FAl~H_2 z^>kcq)f*jLi@k)Y7WP(PH8qeRK}^XDOsXP!u1-<(i#G`qi8AIAB}8(5O$^3~#i~+t zAX%}xWJE09p6+o*&165tS+>}o4l%B!@l-ApY7XluS3>4Y-e?V<>=5Hc=@7#2`;Dwt z!$s{F?rQ;-wOhFC9l__UF29Kh{ixbC#vOeITl#gKxzhSxZ8nqx3x=nuq9%s^0zEQn z|C{(^nAkGvD4199z?{Jk^E8BY0ynJlh^({NwhrT