Skip to content

Commit be5b87e

Browse files
committed
Merge upstream changes from r395
1 parent afef458 commit be5b87e

8 files changed

Lines changed: 148 additions & 31 deletions

File tree

python/phonenumbers/asyoutypeformatter.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,8 @@ def input_digit(self, next_char, remember_position=False):
327327
if self._able_to_format:
328328
self._current_output = self._prefix_before_national_number + temp_national_number
329329
return self._current_output
330-
else: # pragma no cover
331-
self._current_output = temp_national_number
330+
else:
331+
self._current_output = self._accrued_input
332332
return self._current_output
333333
else:
334334
self._current_output = self._attempt_to_choose_formatting_pattern()
@@ -392,8 +392,10 @@ def _attempt_to_choose_formatting_pattern(self):
392392
# number (excluding national prefix) have been entered.
393393
if len(self._national_number) >= _MIN_LEADING_DIGITS_LENGTH:
394394
self._get_available_formats(self._national_number[:_MIN_LEADING_DIGITS_LENGTH])
395-
self._maybe_create_new_template()
396-
return self._input_accrued_national_number()
395+
if self._maybe_create_new_template():
396+
return self._input_accrued_national_number()
397+
else:
398+
return self._accrued_input
397399
else:
398400
return self._prefix_before_national_number + self._national_number
399401

@@ -408,7 +410,7 @@ def _input_accrued_national_number(self):
408410
if self._able_to_format:
409411
return self._prefix_before_national_number + temp_national_number
410412
else:
411-
return temp_national_number
413+
return self.accrued_input
412414
else:
413415
return self._prefix_before_national_number
414416

python/phonenumbers/geocoder.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,12 +137,26 @@ def country_name_for_number(numobj, lang, script=None, region=None):
137137

138138

139139
def description_for_valid_number(numobj, lang, script=None, region=None):
140-
"""Return a text description of a PhoneNumber object for the given language.
140+
"""Return a text description of a PhoneNumber object, in the language
141+
provided.
141142
142143
The description might consist of the name of the country where the phone
143144
number is from and/or the name of the geographical area the phone number
144-
is from. This function assumes the validity of the number passed in has
145-
already been checked.
145+
is from if more detailed information is available.
146+
147+
If the phone number is from the same region as the user, only a
148+
lower-level description will be returned, if one exists. Otherwise, the
149+
phone number's region will be returned, with optionally some more detailed
150+
information.
151+
152+
For example, for a user from the region "US" (United States), we would
153+
show "Mountain View, CA" for a particular number, omitting the United
154+
States from the description. For a user from the United Kingdom (region
155+
"GB"), for the same number we may show "Mountain View, CA, United States"
156+
or even just "United States".
157+
158+
This function assumes the validity of the number passed in has already
159+
been checked.
146160
147161
Arguments:
148162
numobj -- A valid PhoneNumber object for which we want to get a text
@@ -152,16 +166,26 @@ def description_for_valid_number(numobj, lang, script=None, region=None):
152166
script -- A 4-letter titlecase (first letter uppercase, rest lowercase)
153167
ISO script code as defined in ISO 15924, separated by an
154168
underscore (e.g. "Hant")
155-
region -- A 2-letter uppercase ISO 3166-1 country code (e.g. "GB")
169+
region -- The region code for a given user. This region will be omitted
170+
from the description if the phone number comes from this
171+
region. It is a two-letter uppercase ISO country code as
172+
defined by ISO 3166-1.
156173
157174
Returns a text description in the given language code, for the given phone
158175
number, or an empty string if no description is available."""
159-
area_description = area_description_for_number(numobj, lang, script, region)
160-
if area_description != "":
161-
return area_description
176+
number_region = region_code_for_number(numobj)
177+
if region is None or region == number_region:
178+
area_description = area_description_for_number(numobj, lang, script, region)
179+
if area_description != "":
180+
return area_description
181+
else:
182+
# Fall back to the description of the number's region
183+
return country_name_for_number(numobj, lang, script, region)
162184
else:
163-
# Fall back to the description of the number's region
185+
# Otherwise, we just show the region(country) name for now.
164186
return country_name_for_number(numobj, lang, script, region)
187+
# TODO: Concatenate the lower-level and country-name information in an
188+
# appropriate way for each language.
165189

166190

167191
def description_for_number(numobj, lang, script=None, region=None):

python/phonenumbers/phonenumbermatcher.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,9 @@ def _is_latin_letter(cls, letter):
472472
block == Block.COMBINING_DIACRITICAL_MARKS)
473473

474474
@classmethod
475-
def _is_currency_symbol(cls, character):
476-
return Category.get(character) == Category.CURRENCY_SYMBOL
475+
def _is_invalid_punctuation_symbol(cls, character):
476+
return (character == '%' or
477+
Category.get(character) == Category.CURRENCY_SYMBOL)
477478

478479
def _extract_match(self, candidate, offset):
479480
"""Attempts to extract a match from a candidate string.
@@ -579,15 +580,16 @@ def _parse_and_verify(self, candidate, offset):
579580
if (offset > 0 and
580581
not _LEAD_PATTERN.match(candidate)):
581582
previous_char = self.text[offset - 1]
582-
# We return None if it is a latin letter or a currency symbol
583-
if (self._is_latin_letter(previous_char) or
584-
self._is_currency_symbol(previous_char)):
583+
# We return None if it is a latin letter or an invalid
584+
# punctuation symbol
585+
if (self._is_invalid_punctuation_symbol(previous_char) or
586+
self._is_latin_letter(previous_char)):
585587
return None
586588
last_char_index = offset + len(candidate)
587589
if last_char_index < len(self.text):
588590
next_char = self.text[last_char_index]
589-
if (self._is_latin_letter(next_char) or
590-
self._is_currency_symbol(next_char)):
591+
if (self._is_invalid_punctuation_symbol(next_char) or
592+
self._is_latin_letter(next_char)):
591593
return None
592594

593595
numobj = parse(candidate, self.preferred_region, keep_raw_input=True)

python/phonenumbers/phonenumberutil.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@
120120
# For performance reasons, amalgamate both into one map.
121121
_ALPHA_PHONE_MAPPINGS = dict(_ALPHA_MAPPINGS, **_ASCII_DIGITS_MAP)
122122

123+
# A map that contains characters that are essential when dialling. That means
124+
# any of the characters in this map must not be removed from a number when
125+
# dialing, otherwise the call will not reach the intended destination.
126+
_DIALLABLE_CHAR_MAPPINGS = dict({u'+': u'+', u'*': u'*'},
127+
**_ASCII_DIGITS_MAP)
128+
123129
# Separate map of all symbols that we wish to retain when formatting alpha
124130
# numbers. This includes digits, ASCII letters and number grouping symbols
125131
# such as "-" and " ".
@@ -856,7 +862,7 @@ def format_number_for_mobile_dialing(numobj, region_calling_from, with_formattin
856862
857863
Returns the formatted phone number.
858864
"""
859-
region_code = region_code_for_number(numobj)
865+
region_code = region_code_for_country_code(numobj.country_code)
860866
if not _is_valid_region_code(region_code):
861867
if numobj.raw_input is None:
862868
return ""
@@ -867,10 +873,18 @@ def format_number_for_mobile_dialing(numobj, region_calling_from, with_formattin
867873
numobj_no_ext.merge_from(numobj)
868874
numobj_no_ext.extension = None
869875
numobj_type = number_type(numobj_no_ext)
870-
if (region_code == "CO" and region_calling_from == "CO" and
871-
numobj_type == PhoneNumberType.FIXED_LINE):
872-
formatted_number = format_national_number_with_carrier_code(numobj_no_ext,
873-
_COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX)
876+
if region_code == "CO" and region_calling_from == "CO":
877+
if numobj_type == PhoneNumberType.FIXED_LINE:
878+
formatted_number = format_national_number_with_carrier_code(numobj_no_ext,
879+
_COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX)
880+
else:
881+
# E164 doesn't work at all when dialling within Colombia
882+
formatted_number = format_number(numobj_no_ext, PhoneNumberFormat.NATIONAL)
883+
elif region_code == "PE" and region_calling_from == "PE":
884+
# In Peru, numbers cannot be dialled using E164 format from a mobile
885+
# phone for Movistar. Instead they must be dialled in national
886+
# format.
887+
formatted_number = format_number(numobj_no_ext, PhoneNumberFormat.NATIONAL)
874888
elif (region_code == "BR" and region_calling_from == "BR" and
875889
((numobj_type == PhoneNumberType.FIXED_LINE) or
876890
(numobj_type == PhoneNumberType.MOBILE) or
@@ -896,7 +910,8 @@ def format_number_for_mobile_dialing(numobj, region_calling_from, with_formattin
896910
if with_formatting:
897911
return formatted_number
898912
else:
899-
return normalize_digits_only(formatted_number)
913+
return _normalize_helper(formatted_number, _DIALLABLE_CHAR_MAPPINGS,
914+
True) # remove non matches
900915

901916

902917
def format_out_of_country_calling_number(numobj, region_calling_from):
@@ -994,7 +1009,12 @@ def format_in_original_format(numobj, region_calling_from):
9941009
9951010
Returns the formatted phone number in its original number format.
9961011
"""
997-
if numobj.raw_input is not None and not is_valid_number(numobj):
1012+
if (numobj.raw_input is not None and
1013+
(not has_formatting_pattern_for_number(numobj) or not is_valid_number(numobj))):
1014+
# We check if we have the formatting pattern because without that, we
1015+
# might format the number as a group without national prefix. We also
1016+
# want to check the validity of the number because we don't want to
1017+
# risk formatting the number if we don't really understand it.
9981018
return numobj.raw_input
9991019
if numobj.country_code_source is None:
10001020
return format_number(numobj, PhoneNumberFormat.NATIONAL)
@@ -1011,6 +1031,16 @@ def format_in_original_format(numobj, region_calling_from):
10111031
return format_number(numobj, PhoneNumberFormat.NATIONAL)
10121032

10131033

1034+
def has_formatting_pattern_for_number(numobj):
1035+
phone_number_region = region_code_for_country_code(numobj.country_code)
1036+
metadata = PhoneMetadata.region_metadata.get(phone_number_region, None)
1037+
if metadata is None:
1038+
return False
1039+
national_number = national_significant_number(numobj)
1040+
format_rule = _choose_formatting_pattern_for_number(metadata.number_format, national_number)
1041+
return format_rule != None
1042+
1043+
10141044
def format_out_of_country_keeping_alpha_chars(numobj, region_calling_from):
10151045
"""Formats a phone number for out-of-country dialing purposes.
10161046

python/tests/asyoutypetest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ def testTooLongNumberMatchingMultipleLeadingDigits(self):
9898
self.assertEqual("+81 90 1234 5678", formatter.input_digit('8'))
9999
self.assertEqual("+81 90 12 345 6789", formatter.input_digit('9'))
100100
self.assertEqual("+81901234567890", formatter.input_digit('0'))
101+
self.assertEqual("+819012345678901", formatter.input_digit('1'))
101102

102103
def testAYTFUS(self):
103104
formatter = AsYouTypeFormatter("US")
@@ -717,6 +718,15 @@ def testAYTFMultipleLeadingDigitPatterns(self):
717718
self.assertEqual("+81 222 12 567", formatter.input_digit('7'))
718719
self.assertEqual("+81 222 12 5678", formatter.input_digit('8'))
719720

721+
# 011113
722+
formatter.clear()
723+
self.assertEqual("0", formatter.input_digit('0'))
724+
self.assertEqual("01", formatter.input_digit('1'))
725+
self.assertEqual("011", formatter.input_digit('1'))
726+
self.assertEqual("011 1", formatter.input_digit('1'))
727+
self.assertEqual("011 11", formatter.input_digit('1'))
728+
self.assertEqual("011113", formatter.input_digit('3'))
729+
720730
# +81 3332 2 5678
721731
formatter.clear()
722732
self.assertEqual("+", formatter.input_digit('+'))

python/tests/geocodertest.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ def insert_test_geodata():
6262
_ENGLISH = "en"
6363
_KOREAN = "ko"
6464
_GERMAN = "de"
65+
_FRENCH = "fr"
6566
_USA = "US"
6667

6768

@@ -127,6 +128,24 @@ def testGetDescriptionForFallBack(self):
127128
self.assertEqual(u"\uB300\uD55C\uBBFC\uAD6D",
128129
geocoder.description_for_number(KO_NUMBER3, _KOREAN))
129130

131+
def testGetDescriptionForNumberWithUserRegion(self):
132+
# User in Italy, American number. We should just show United States, in
133+
# German, and not more detailed information.
134+
self.assertEqual("Vereinigte Staaten von Amerika",
135+
geocoder.description_for_number(US_NUMBER1, _GERMAN, region="IT"))
136+
# Unknown region - should just show country name.
137+
self.assertEqual("Vereinigte Staaten von Amerika",
138+
geocoder.description_for_number(US_NUMBER1, _GERMAN, region="ZZ"))
139+
# User in the States, language German, should show detailed data.
140+
self.assertEqual("Kalifornien",
141+
geocoder.description_for_number(US_NUMBER1, _GERMAN, region="US"))
142+
# User in the States, language French, no data for French, so we fallback
143+
# to English detailed data.
144+
self.assertEqual("CA",
145+
geocoder.description_for_number(US_NUMBER1, _FRENCH, region="US"))
146+
# Invalid number - return an empty string.
147+
self.assertEqual("", geocoder.description_for_number(US_INVALID_NUMBER, _ENGLISH, region="US"))
148+
130149
def testGetDescriptionForInvalidNumber(self):
131150
self.assertEqual("", geocoder.description_for_number(KO_INVALID_NUMBER, _ENGLISH))
132151
self.assertEqual("", geocoder.description_for_number(US_INVALID_NUMBER, _ENGLISH))
@@ -139,10 +158,12 @@ def testCoverage(self):
139158
TEST_GEOCODE_DATA['1650960'] = {'en': u'Mountain View, CA',
140159
"en_GB": u'Mountain View California',
141160
"en_Latn": u'MountainView'}
142-
self.assertEqual("Mountain View California",
161+
# The following test might one day return "Mountain View California"
162+
self.assertEqual("United States",
143163
geocoder.description_for_number(US_NUMBER2, _ENGLISH, region="GB"))
144164
self.assertEqual("MountainView",
145165
geocoder.description_for_number(US_NUMBER2, _ENGLISH, script="Latn"))
146-
self.assertEqual("MountainView",
166+
# The following test might one day return "MountainView"
167+
self.assertEqual("United States",
147168
geocoder.description_for_number(US_NUMBER2, _ENGLISH, script="Latn", region="GB"))
148169
TEST_GEOCODE_DATA['1650960'] = {'en': u'Mountain View, CA'}

python/tests/phonenumbermatchertest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,12 @@ def testMoneyNotSeenAsPhoneNumber(self):
423423
possibleOnlyContexts.append(NumberContext(u"\u00A5", "")) # Yen sign
424424
self.findMatchesInContexts(possibleOnlyContexts, False, True)
425425

426+
def testPercentageNotSeenAsPhoneNumber(self):
427+
possibleOnlyContexts = []
428+
possibleOnlyContexts.append(NumberContext("", "%"))
429+
# Numbers followed by % should be dropped.
430+
self.findMatchesInContexts(possibleOnlyContexts, False, True)
431+
426432
def testPhoneNumberWithLeadingOrTrailingMoneyMatches(self):
427433
# Because of the space after the 20 (or before the 100) these dollar
428434
# amounts should not stop the actual number from being found.

python/tests/phonenumberutiltest.py

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def insert_test_metadata():
7777
GB_NUMBER = FrozenPhoneNumber(country_code=44, national_number=2070313000L)
7878
IT_MOBILE = FrozenPhoneNumber(country_code=39, national_number=345678901L)
7979
IT_NUMBER = FrozenPhoneNumber(country_code=39, national_number=236618300L, italian_leading_zero=True)
80+
JP_STAR_NUMBER = FrozenPhoneNumber(country_code=81, national_number=2345L)
8081
# Numbers to test the formatting rules from Mexico.
8182
MX_MOBILE1 = FrozenPhoneNumber(country_code=52, national_number=12345678900L)
8283
MX_MOBILE2 = FrozenPhoneNumber(country_code=52, national_number=15512345678L)
@@ -594,7 +595,8 @@ def testFormatNumberForMobileDialing(self):
594595
# US toll free numbers are marked as noInternationalDialling in the
595596
# test metadata for testing purposes.
596597
self.assertEqual("800 253 0000",
597-
phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "US", True))
598+
phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "US",
599+
True)) # Keep formatting
598600
self.assertEqual("", phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "CN", True))
599601
self.assertEqual("+1 650 253 0000",
600602
phonenumbers.format_number_for_mobile_dialing(US_NUMBER, "US", True))
@@ -604,12 +606,27 @@ def testFormatNumberForMobileDialing(self):
604606
self.assertEqual("+1 650 253 0000",
605607
phonenumbers.format_number_for_mobile_dialing(usNumberWithExtn, "US", True))
606608
self.assertEqual("8002530000",
607-
phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "US", False))
609+
phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "US",
610+
False)) # Remove formatting
608611
self.assertEqual("", phonenumbers.format_number_for_mobile_dialing(US_TOLLFREE, "CN", False))
609612
self.assertEqual("+16502530000",
610613
phonenumbers.format_number_for_mobile_dialing(US_NUMBER, "US", False))
611614
self.assertEqual("+16502530000",
612615
phonenumbers.format_number_for_mobile_dialing(usNumberWithExtn, "US", False))
616+
617+
# An invalid US number, which is one digit too long.
618+
self.assertEqual("+165025300001",
619+
phonenumbers.format_number_for_mobile_dialing(US_LONG_NUMBER, "US", False))
620+
self.assertEqual("+1 65025300001",
621+
phonenumbers.format_number_for_mobile_dialing(US_LONG_NUMBER, "US", True))
622+
623+
# Star numbers. In real life they appear in Israel, but we have them
624+
# in JP in our test metadata.
625+
self.assertEqual("*2345",
626+
phonenumbers.format_number_for_mobile_dialing(JP_STAR_NUMBER, "JP", False))
627+
self.assertEqual("*2345",
628+
phonenumbers.format_number_for_mobile_dialing(JP_STAR_NUMBER, "JP", True))
629+
613630
# Python version extra tests
614631
number = PhoneNumber()
615632
number.merge_from(XY_NUMBER)
@@ -720,6 +737,11 @@ def testFormatUsingOriginalNumberFormat(self):
720737
number7 = phonenumbers.parse("7345678901", "US")
721738
self.assertEqual("734 567 8901", phonenumbers.format_in_original_format(number7, "US"))
722739

740+
# This number is valid, but we don't have a formatting pattern for
741+
# it. Fall back to the raw input.
742+
number8 = phonenumbers.parse("02-4567-8900", "KR", keep_raw_input=True)
743+
self.assertEqual("02-4567-8900", phonenumbers.format_in_original_format(number8, "KR"))
744+
723745
# Python version extra tests
724746
number8 = phonenumbers.parse("87654321", None, keep_raw_input=True, _check_region=False)
725747
self.assertEqual("87654321", phonenumbers.format_in_original_format(number8, "US"))

0 commit comments

Comments
 (0)