i18n_phone_numbers 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/i18n_phone_numbers/constants.rb +58 -0
- data/lib/i18n_phone_numbers/metadata.rb +345 -0
- data/lib/i18n_phone_numbers/number_format.rb +87 -0
- data/lib/i18n_phone_numbers/phone_number.rb +41 -0
- data/lib/i18n_phone_numbers/phone_number_desc.rb +33 -0
- data/lib/i18n_phone_numbers/util.rb +1237 -0
- data/lib/i18n_phone_numbers.rb +8 -0
- metadata +53 -0
@@ -0,0 +1,1237 @@
|
|
1
|
+
module I18nPhoneNumbers
|
2
|
+
|
3
|
+
module Util
|
4
|
+
|
5
|
+
# TODO : wait for ruby 1.9 to use unicode
|
6
|
+
#
|
7
|
+
# PLUS_CHARS_ = '+\uFF0B'
|
8
|
+
# VALID_DIGITS_ = '0-9\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9'
|
9
|
+
# VALID_PUNCTUATION = '-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F \u00A0\u200B\u2060\u3000()' +
|
10
|
+
# '\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E'
|
11
|
+
|
12
|
+
# TODO ? : support alpha in numbers ?
|
13
|
+
# VALID_ALPHA_ = 'A-Za-z'
|
14
|
+
|
15
|
+
# TODO ? : support extensions ?
|
16
|
+
# KNOWN_EXTN_PATTERNS_ = ...
|
17
|
+
|
18
|
+
PLUS_SIGN = '+'
|
19
|
+
STAR_SIGN = '*'
|
20
|
+
PLUS_CHARS_ = '+'
|
21
|
+
PLUS_CHARS_PATTERN = Regexp.compile('[' + PLUS_CHARS_ + ']+')
|
22
|
+
LEADING_PLUS_CHARS_PATTERN_ = Regexp.compile('\A[' + PLUS_CHARS_ + ']+')
|
23
|
+
VALID_DIGITS_ = '0-9'
|
24
|
+
VALID_ALPHA_ = '' # we don't support it yet
|
25
|
+
VALID_PUNCTUATION = '-x ().\\/\\[\\]~' # add "\-escapes" for [ and ] (will be further included in a [] group in another regexp)
|
26
|
+
KNOWN_EXTN_PATTERNS_ = '' # we don't support extensions yet
|
27
|
+
VALID_START_CHAR_PATTERN = Regexp.compile('[' + PLUS_CHARS_ + VALID_DIGITS_ + ']')
|
28
|
+
SECOND_NUMBER_START_PATTERN_ = /[\\\/] *x/
|
29
|
+
UNWANTED_END_CHAR_PATTERN_ = Regexp.compile('[^' + VALID_DIGITS_ + VALID_ALPHA_ + '#]+\z')
|
30
|
+
CAPTURING_DIGIT_PATTERN = /([0-9])/
|
31
|
+
# This was originally set to \\1 but there are some countries for which the
|
32
|
+
# first group is not used in the national pattern (e.g. Argentina) so the \\1
|
33
|
+
# group does not match correctly. Therefore, we use \d, so that the first
|
34
|
+
# group actually used in the pattern will be matched.
|
35
|
+
FIRST_GROUP_PATTERN_ = /(\\\d)/
|
36
|
+
NP_PATTERN_ = /\$NP/
|
37
|
+
FG_PATTERN_ = /\$FG/
|
38
|
+
CC_PATTERN_ = /\$CC/
|
39
|
+
REGION_CODE_FOR_NON_GEO_ENTITY = '001'
|
40
|
+
|
41
|
+
# Regular expression of viable phone numbers. This is location independent.
|
42
|
+
# -> plus_sign*(([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])*
|
43
|
+
VALID_PHONE_NUMBER_ = '[' + PLUS_CHARS_ + ']*(?:[' +
|
44
|
+
VALID_PUNCTUATION + STAR_SIGN + ']*[' +
|
45
|
+
VALID_DIGITS_ + ']){3,}[' +
|
46
|
+
VALID_PUNCTUATION +
|
47
|
+
STAR_SIGN +
|
48
|
+
VALID_ALPHA_ +
|
49
|
+
VALID_DIGITS_ + ']*'
|
50
|
+
|
51
|
+
VALID_PHONE_NUMBER_PATTERN_ = Regexp.compile('\A' + VALID_PHONE_NUMBER_ + '(?:' + KNOWN_EXTN_PATTERNS_ + ')?' + '\z', 'i')
|
52
|
+
|
53
|
+
|
54
|
+
MAX_LENGTH_COUNTRY_CODE_ = 3
|
55
|
+
UNKNOWN_REGION_ = 'ZZ'
|
56
|
+
# The minimum and maximum length of the national significant number.
|
57
|
+
MIN_LENGTH_FOR_NSN_ = 3
|
58
|
+
# The ITU says the maximum length should be 15, but we have found longer numbers in Germany.
|
59
|
+
MAX_LENGTH_FOR_NSN_ = 16
|
60
|
+
|
61
|
+
|
62
|
+
DIGIT_MAPPINGS = {
|
63
|
+
'0' => '0',
|
64
|
+
'1' => '1',
|
65
|
+
'2' => '2',
|
66
|
+
'3' => '3',
|
67
|
+
'4' => '4',
|
68
|
+
'5' => '5',
|
69
|
+
'6' => '6',
|
70
|
+
'7' => '7',
|
71
|
+
'8' => '8',
|
72
|
+
'9' => '9' #,
|
73
|
+
|
74
|
+
# TODO : wait for ruby 1.9 to use unicode
|
75
|
+
|
76
|
+
# '\uFF10' => '0', # Fullwidth digit 0
|
77
|
+
# '\uFF11' => '1', # Fullwidth digit 1
|
78
|
+
# '\uFF12' => '2', # Fullwidth digit 2
|
79
|
+
# '\uFF13' => '3', # Fullwidth digit 3
|
80
|
+
# '\uFF14' => '4', # Fullwidth digit 4
|
81
|
+
# '\uFF15' => '5', # Fullwidth digit 5
|
82
|
+
# '\uFF16' => '6', # Fullwidth digit 6
|
83
|
+
# '\uFF17' => '7', # Fullwidth digit 7
|
84
|
+
# '\uFF18' => '8', # Fullwidth digit 8
|
85
|
+
# '\uFF19' => '9', # Fullwidth digit 9
|
86
|
+
# '\u0660' => '0', # Arabic-indic digit 0
|
87
|
+
# '\u0661' => '1', # Arabic-indic digit 1
|
88
|
+
# '\u0662' => '2', # Arabic-indic digit 2
|
89
|
+
# '\u0663' => '3', # Arabic-indic digit 3
|
90
|
+
# '\u0664' => '4', # Arabic-indic digit 4
|
91
|
+
# '\u0665' => '5', # Arabic-indic digit 5
|
92
|
+
# '\u0666' => '6', # Arabic-indic digit 6
|
93
|
+
# '\u0667' => '7', # Arabic-indic digit 7
|
94
|
+
# '\u0668' => '8', # Arabic-indic digit 8
|
95
|
+
# '\u0669' => '9', # Arabic-indic digit 9
|
96
|
+
# '\u06F0' => '0', # Eastern-Arabic digit 0
|
97
|
+
# '\u06F1' => '1', # Eastern-Arabic digit 1
|
98
|
+
# '\u06F2' => '2', # Eastern-Arabic digit 2
|
99
|
+
# '\u06F3' => '3', # Eastern-Arabic digit 3
|
100
|
+
# '\u06F4' => '4', # Eastern-Arabic digit 4
|
101
|
+
# '\u06F5' => '5', # Eastern-Arabic digit 5
|
102
|
+
# '\u06F6' => '6', # Eastern-Arabic digit 6
|
103
|
+
# '\u06F7' => '7', # Eastern-Arabic digit 7
|
104
|
+
# '\u06F8' => '8', # Eastern-Arabic digit 8
|
105
|
+
# '\u06F9' => '9' # Eastern-Arabic digit 9
|
106
|
+
}
|
107
|
+
|
108
|
+
class << self
|
109
|
+
|
110
|
+
def parse(numberToParse, alpha2)
|
111
|
+
|
112
|
+
return self.parseHelper_(numberToParse, alpha2.upcase, false, true)
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
def parseAndKeepRawInput(numberToParse, alpha2)
|
117
|
+
|
118
|
+
if !isValidRegionCode_(alpha2)
|
119
|
+
if (numberToParse.length > 0 && numberToParse[0..0] != PLUS_SIGN)
|
120
|
+
raise "INVALID_COUNTRY_CODE"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
return self.parseHelper_(numberToParse, alpha2.upcase, true, true)
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def parseHelper_(numberToParse, defaultRegion, keepRawInput, checkRegion)
|
130
|
+
|
131
|
+
if numberToParse.blank?
|
132
|
+
raise "NOT_A_NUMBER"
|
133
|
+
end
|
134
|
+
|
135
|
+
nationalNumber = extractPossibleNumber(numberToParse)
|
136
|
+
|
137
|
+
if !isViablePhoneNumber(nationalNumber)
|
138
|
+
raise "NOT_A_NUMBER"
|
139
|
+
end
|
140
|
+
|
141
|
+
if checkRegion && !checkRegionForParsing_(nationalNumber, defaultRegion)
|
142
|
+
raise "INVALID_COUNTRY_CODE"
|
143
|
+
end
|
144
|
+
|
145
|
+
# build PhoneNumber instance
|
146
|
+
phoneNumber = I18nPhoneNumbers::PhoneNumber.new
|
147
|
+
|
148
|
+
if keepRawInput
|
149
|
+
phoneNumber.raw_input = numberToParse
|
150
|
+
end
|
151
|
+
|
152
|
+
# Attempt to parse extension first, since it doesn't require
|
153
|
+
# country-specific data and we want to have the non-normalised number here.
|
154
|
+
#
|
155
|
+
# phoneNumber.extension = self.maybeStripExtension(nationalNumber)
|
156
|
+
|
157
|
+
|
158
|
+
regionMetadata = I18nPhoneNumbers::Metadata.countryToMetadata[defaultRegion]
|
159
|
+
|
160
|
+
# Check to see if the number is given in international format so we know
|
161
|
+
# whether this number is from the default country or not.
|
162
|
+
normalizedNationalNumber = ''
|
163
|
+
|
164
|
+
countryCode = maybeExtractCountryCode(nationalNumber, regionMetadata, normalizedNationalNumber, keepRawInput, phoneNumber)
|
165
|
+
|
166
|
+
|
167
|
+
if countryCode != 0
|
168
|
+
|
169
|
+
phoneNumberRegion = getRegionCodeForCountryCode(countryCode)
|
170
|
+
|
171
|
+
if phoneNumberRegion != defaultRegion
|
172
|
+
regionMetadata = getMetadataForRegion(phoneNumberRegion)
|
173
|
+
end
|
174
|
+
|
175
|
+
else
|
176
|
+
# If no extracted country code, use the region supplied instead. The
|
177
|
+
# national number is just the normalized version of the number we were
|
178
|
+
# given to parse
|
179
|
+
normalizeSB_(nationalNumber)
|
180
|
+
|
181
|
+
normalizedNationalNumber.replace(nationalNumber.dup)
|
182
|
+
|
183
|
+
if !defaultRegion.nil?
|
184
|
+
countryCode = regionMetadata.country_code
|
185
|
+
phoneNumber.country_code = countryCode
|
186
|
+
elsif keepRawInput
|
187
|
+
phoneNumber.country_code_source = nil
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
if normalizedNationalNumber.length < MIN_LENGTH_FOR_NSN_
|
193
|
+
raise "TOO_SHORT_NSN"
|
194
|
+
end
|
195
|
+
|
196
|
+
if !regionMetadata.nil?
|
197
|
+
|
198
|
+
carrierCode = ''
|
199
|
+
|
200
|
+
maybeStripNationalPrefixAndCarrierCode(normalizedNationalNumber, regionMetadata, carrierCode)
|
201
|
+
|
202
|
+
if keepRawInput
|
203
|
+
phoneNumber.preferred_domestic_carrier_code = carrierCode
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
normalizedNationalNumberStr = normalizedNationalNumber.dup
|
209
|
+
|
210
|
+
lengthOfNationalNumber = normalizedNationalNumberStr.length
|
211
|
+
|
212
|
+
|
213
|
+
raise "TOO_SHORT_NSN" if lengthOfNationalNumber < MIN_LENGTH_FOR_NSN_
|
214
|
+
|
215
|
+
raise "TOO_LONG" if lengthOfNationalNumber > MAX_LENGTH_FOR_NSN_
|
216
|
+
|
217
|
+
if normalizedNationalNumberStr[0..0] == '0' && regionMetadata.leading_zero_possible
|
218
|
+
phoneNumber.italian_leading_zero = true
|
219
|
+
end
|
220
|
+
|
221
|
+
phoneNumber.national_number = normalizedNationalNumberStr.to_i # to_i, really ?
|
222
|
+
|
223
|
+
return phoneNumber
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
|
229
|
+
# Checks to see that the region code used is valid, or if it is not valid, that the number to
|
230
|
+
# parse starts with a + symbol so that we can attempt to infer the region from the number.
|
231
|
+
# Returns false if it cannot use the region provided and the region cannot be inferred.
|
232
|
+
def checkRegionForParsing_(numberToParse, defaultRegion)
|
233
|
+
|
234
|
+
return isValidRegionCode_(defaultRegion) ||
|
235
|
+
(!numberToParse.nil? && numberToParse.length > 0 && numberToParse.match(LEADING_PLUS_CHARS_PATTERN_))
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
# Tries to extract a country code from a number. This method will return zero
|
241
|
+
# if no country code is considered to be present. Country codes are extracted
|
242
|
+
# in the following ways:
|
243
|
+
# - by stripping the international dialing prefix of the country the person is
|
244
|
+
# dialing from, if this is present in the number, and looking at the next
|
245
|
+
# digits
|
246
|
+
# - by stripping the '+' sign if present and then looking at the next digits
|
247
|
+
# - by comparing the start of the number and the country code of the default
|
248
|
+
# region. If the number is not considered possible for the numbering plan of
|
249
|
+
# the default region initially, but starts with the country code of this
|
250
|
+
# region, validation will be reattempted after stripping this country code. If
|
251
|
+
# this number is considered a possible number, then the first digits will be
|
252
|
+
# considered the country code and removed as such.
|
253
|
+
#
|
254
|
+
# It will throw a i18n.phonenumbers.Error if the number starts with a '+' but
|
255
|
+
# the country code supplied after this does not match that of any known
|
256
|
+
# country.
|
257
|
+
#
|
258
|
+
# @param {string} number non-normalized telephone number that we wish to
|
259
|
+
# extract a country code from - may begin with '+'.
|
260
|
+
# @param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata
|
261
|
+
# about the region this number may be from.
|
262
|
+
# @param {!goog.string.StringBuffer} nationalNumber a string buffer to store
|
263
|
+
# the national significant number in, in the case that a country code was
|
264
|
+
# extracted. The number is appended to any existing contents. If no country
|
265
|
+
# code was extracted, this will be left unchanged.
|
266
|
+
# @param {boolean} keepRawInput true if the country_code_source and
|
267
|
+
# preferred_carrier_code fields of phoneNumber should be populated.
|
268
|
+
# of phoneNumber should be populated.
|
269
|
+
# @param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object
|
270
|
+
# that needs to be populated with country code and country code source.
|
271
|
+
# Note the country code is always populated, whereas country code source is
|
272
|
+
# only populated when keepCountryCodeSource is true.
|
273
|
+
# @return {number} the country code extracted or 0 if none could be extracted.
|
274
|
+
# @throws {i18n.phonenumbers.Error}
|
275
|
+
def maybeExtractCountryCode(number, defaultRegionMetadata, nationalNumber, keepRawInput, phoneNumber)
|
276
|
+
|
277
|
+
if (number.length == 0)
|
278
|
+
return 0
|
279
|
+
end
|
280
|
+
|
281
|
+
fullNumber = number.dup
|
282
|
+
|
283
|
+
possibleCountryIddPrefix = nil
|
284
|
+
|
285
|
+
if !defaultRegionMetadata.nil?
|
286
|
+
possibleCountryIddPrefix = defaultRegionMetadata.international_prefix
|
287
|
+
end
|
288
|
+
|
289
|
+
if possibleCountryIddPrefix.nil?
|
290
|
+
possibleCountryIddPrefix = 'NonMatch' # put something that will NEVER match
|
291
|
+
end
|
292
|
+
|
293
|
+
countryCodeSource = maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix)
|
294
|
+
|
295
|
+
if keepRawInput
|
296
|
+
phoneNumber.country_code_source = countryCodeSource
|
297
|
+
end
|
298
|
+
|
299
|
+
|
300
|
+
if countryCodeSource != I18nPhoneNumbers::CountryCodeSource::FROM_DEFAULT_COUNTRY
|
301
|
+
|
302
|
+
if fullNumber.length < MIN_LENGTH_FOR_NSN_
|
303
|
+
raise "TOO_SHORT_AFTER_IDD"
|
304
|
+
end
|
305
|
+
|
306
|
+
potentialCountryCode = extractCountryCode(fullNumber, nationalNumber)
|
307
|
+
|
308
|
+
if potentialCountryCode != 0
|
309
|
+
phoneNumber.country_code = potentialCountryCode
|
310
|
+
return potentialCountryCode
|
311
|
+
end
|
312
|
+
|
313
|
+
# If this fails, they must be using a strange country code that we don't
|
314
|
+
# recognize, or that doesn't exist.
|
315
|
+
raise "INVALID_COUNTRY_CODE"
|
316
|
+
|
317
|
+
elsif !defaultRegionMetadata.nil?
|
318
|
+
|
319
|
+
# Check to see if the number starts with the country calling code for the
|
320
|
+
# default region. If so, we remove the country calling code, and do some
|
321
|
+
# checks on the validity of the number before and after.
|
322
|
+
defaultCountryCode = defaultRegionMetadata.country_code
|
323
|
+
|
324
|
+
defaultCountryCodeString = defaultCountryCode.to_s
|
325
|
+
|
326
|
+
normalizedNumber = fullNumber.dup
|
327
|
+
|
328
|
+
if normalizedNumber.match(Regexp.new('\A' + defaultCountryCodeString))
|
329
|
+
|
330
|
+
potentialNationalNumber = normalizedNumber[(defaultCountryCodeString.length)..-1]
|
331
|
+
|
332
|
+
generalDesc = defaultRegionMetadata.general_desc
|
333
|
+
|
334
|
+
validNumberPattern = Regexp.new(generalDesc.national_number_pattern)
|
335
|
+
|
336
|
+
maybeStripNationalPrefixAndCarrierCode(potentialNationalNumber, defaultRegionMetadata, nil)
|
337
|
+
|
338
|
+
potentialNationalNumberStr = potentialNationalNumber.dup
|
339
|
+
|
340
|
+
possibleNumberPattern = generalDesc.possible_number_pattern
|
341
|
+
|
342
|
+
# If the number was not valid before but is valid now, or if it was too
|
343
|
+
# long before, we consider the number with the country calling code
|
344
|
+
# stripped to be a better result and keep that instead.
|
345
|
+
if (!matchesEntirely_(validNumberPattern, fullNumber) && matchesEntirely_(validNumberPattern, potentialNationalNumberStr)) ||
|
346
|
+
(testNumberLengthAgainstPattern_(possibleNumberPattern, fullNumber) == "TOO_LONG")
|
347
|
+
|
348
|
+
nationalNumber = nationalNumber.replace(potentialNationalNumberStr)
|
349
|
+
|
350
|
+
if keepRawInput
|
351
|
+
phoneNumber.country_code_source = I18nPhoneNumbers::CountryCodeSource::FROM_NUMBER_WITHOUT_PLUS_SIGN
|
352
|
+
end
|
353
|
+
|
354
|
+
phoneNumber.country_code = defaultCountryCode
|
355
|
+
|
356
|
+
return defaultCountryCode
|
357
|
+
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
# No country code present.
|
364
|
+
phoneNumber.country_code = 0
|
365
|
+
return 0
|
366
|
+
|
367
|
+
end
|
368
|
+
|
369
|
+
|
370
|
+
def testNumberLengthAgainstPattern_(numberPattern, number)
|
371
|
+
|
372
|
+
if matchesEntirely_(numberPattern, number)
|
373
|
+
return "IS_POSSIBLE"
|
374
|
+
end
|
375
|
+
|
376
|
+
return number.match('\A' + numberPattern) ? "TOO_LONG" : "TOO_SHORT"
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
# Strips any international prefix (such as +, 00, 011) present in the number
|
382
|
+
# provided, normalizes the resulting number, and indicates if an international
|
383
|
+
# prefix was present.
|
384
|
+
#
|
385
|
+
# @param {!goog.string.StringBuffer} number the non-normalized telephone number
|
386
|
+
# that we wish to strip any international dialing prefix from.
|
387
|
+
# @param {string} possibleIddPrefix the international direct dialing prefix
|
388
|
+
# from the country we think this number may be dialed in.
|
389
|
+
# @return {i18n.phonenumbers.PhoneNumber.CountryCodeSource} the corresponding
|
390
|
+
# CountryCodeSource if an international dialing prefix could be removed
|
391
|
+
# from the number, otherwise CountryCodeSource::FROM_DEFAULT_COUNTRY if
|
392
|
+
# the number did not seem to be in international format.
|
393
|
+
def maybeStripInternationalPrefixAndNormalize(number, possibleIddPrefix)
|
394
|
+
|
395
|
+
numberStr = number.dup
|
396
|
+
|
397
|
+
if numberStr.length == 0
|
398
|
+
return I18nPhoneNumbers::CountryCodeSource::FROM_DEFAULT_COUNTRY
|
399
|
+
end
|
400
|
+
|
401
|
+
# Check to see if the number begins with one or more plus signs.
|
402
|
+
if numberStr.match(LEADING_PLUS_CHARS_PATTERN_)
|
403
|
+
|
404
|
+
numberStr = numberStr.sub(LEADING_PLUS_CHARS_PATTERN_, '') # strip leading +
|
405
|
+
|
406
|
+
# Can now normalize the rest of the number since we've consumed the "+"
|
407
|
+
# sign at the start.
|
408
|
+
number.replace(normalize(numberStr))
|
409
|
+
return I18nPhoneNumbers::CountryCodeSource::FROM_NUMBER_WITH_PLUS_SIGN
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
# Attempt to parse the first digits as an international prefix.
|
414
|
+
iddPattern = Regexp.new(possibleIddPrefix)
|
415
|
+
|
416
|
+
normalizeSB_(number)
|
417
|
+
|
418
|
+
return parsePrefixAsIdd_(iddPattern, number) ?
|
419
|
+
I18nPhoneNumbers::CountryCodeSource::FROM_NUMBER_WITH_IDD :
|
420
|
+
I18nPhoneNumbers::CountryCodeSource::FROM_DEFAULT_COUNTRY
|
421
|
+
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
# Strips the IDD from the start of the number if present. Helper function used
|
426
|
+
# by maybeStripInternationalPrefixAndNormalize.
|
427
|
+
#
|
428
|
+
# @param {RegExp} iddPattern the regular expression for the international
|
429
|
+
# prefix.
|
430
|
+
# @param {!goog.string.StringBuffer} number the phone number that we wish to
|
431
|
+
# strip any international dialing prefix from.
|
432
|
+
# @return {boolean} true if an international prefix was present.
|
433
|
+
# @private
|
434
|
+
def parsePrefixAsIdd_(iddPattern, number)
|
435
|
+
|
436
|
+
if number.index(iddPattern) == 0
|
437
|
+
|
438
|
+
matchEnd = number.match(iddPattern)[0].length
|
439
|
+
|
440
|
+
matchedGroups = number[matchEnd..-1].match(CAPTURING_DIGIT_PATTERN)
|
441
|
+
|
442
|
+
if matchedGroups && matchedGroups[1] == '0'
|
443
|
+
return false
|
444
|
+
end
|
445
|
+
|
446
|
+
number.replace(number[matchEnd..-1])
|
447
|
+
return true
|
448
|
+
end
|
449
|
+
|
450
|
+
return false
|
451
|
+
|
452
|
+
end
|
453
|
+
|
454
|
+
|
455
|
+
# Normalizes a string of characters representing a phone number. This is a
|
456
|
+
# wrapper for normalize(String number) but does in-place normalization of the
|
457
|
+
# StringBuffer provided.
|
458
|
+
#
|
459
|
+
# @param {!goog.string.StringBuffer} number a StringBuffer of characters
|
460
|
+
# representing a phone number that will be normalized in place.
|
461
|
+
# @private
|
462
|
+
def normalizeSB_(number)
|
463
|
+
|
464
|
+
normalizedNumber = normalize(number)
|
465
|
+
number.replace(normalizedNumber)
|
466
|
+
|
467
|
+
end
|
468
|
+
|
469
|
+
|
470
|
+
|
471
|
+
# Normalizes a string of characters representing a phone number. This performs
|
472
|
+
# the following conversions:
|
473
|
+
# - Wide-ascii digits are converted to normal ASCII (European) digits.
|
474
|
+
# - Punctuation is stripped.
|
475
|
+
# - Arabic-Indic numerals are converted to European numerals.
|
476
|
+
#
|
477
|
+
# @param {string} number a string of characters representing a phone number.
|
478
|
+
# @return {string} the normalized string version of the phone number.
|
479
|
+
def normalize(number)
|
480
|
+
return normalizeHelper_(number, DIGIT_MAPPINGS, true)
|
481
|
+
end
|
482
|
+
|
483
|
+
|
484
|
+
# Normalizes a string of characters representing a phone number by replacing
|
485
|
+
# all characters found in the accompanying map with the values therein, and
|
486
|
+
# stripping all other characters if removeNonMatches is true.
|
487
|
+
#
|
488
|
+
# @param {string} number a string of characters representing a phone number.
|
489
|
+
# @param {!Object} normalizationReplacements a mapping of characters to what
|
490
|
+
# they should be replaced by in the normalized version of the phone number.
|
491
|
+
# @param {boolean} removeNonMatches indicates whether characters that are not
|
492
|
+
# able to be replaced should be stripped from the number. If this is false,
|
493
|
+
# they will be left unchanged in the number.
|
494
|
+
# @return {string} the normalized string version of the phone number.
|
495
|
+
# @private
|
496
|
+
def normalizeHelper_(number, normalizationReplacements = DIGIT_MAPPINGS, removeNonMatches = true)
|
497
|
+
|
498
|
+
normalizedNumber = ''
|
499
|
+
|
500
|
+
number.each_char do |character|
|
501
|
+
|
502
|
+
newDigit = normalizationReplacements[character.upcase()]
|
503
|
+
|
504
|
+
if !newDigit.nil?
|
505
|
+
normalizedNumber << newDigit
|
506
|
+
elsif !removeNonMatches
|
507
|
+
normalizedNumber << character
|
508
|
+
end
|
509
|
+
# If neither of the above are true, we remove this character.
|
510
|
+
end
|
511
|
+
|
512
|
+
return normalizedNumber
|
513
|
+
end
|
514
|
+
|
515
|
+
|
516
|
+
# Check whether the entire input sequence can be matched against the regular
|
517
|
+
# expression.
|
518
|
+
#
|
519
|
+
# @param {RegExp|string} regex the regular expression to match against.
|
520
|
+
# @param {string} str the string to test.
|
521
|
+
# @return {boolean} true if str can be matched entirely against regex.
|
522
|
+
# @private
|
523
|
+
def matchesEntirely_(regex, str)
|
524
|
+
|
525
|
+
matchedGroups = str.match(regex) # regex is a string, match will correctly convert it into a regex first
|
526
|
+
|
527
|
+
return !!(matchedGroups && matchedGroups[0].length == str.length)
|
528
|
+
|
529
|
+
end
|
530
|
+
|
531
|
+
|
532
|
+
def extractCountryCode(fullNumber, nationalNumber)
|
533
|
+
|
534
|
+
[fullNumber.length, MAX_LENGTH_COUNTRY_CODE_].min.times do |i|
|
535
|
+
|
536
|
+
potentialCountryCode = fullNumber[0..i].to_i
|
537
|
+
|
538
|
+
if I18nPhoneNumbers::Metadata.countryCodeToRegionCodeMap.has_key?(potentialCountryCode)
|
539
|
+
nationalNumber.replace(fullNumber[(i+1)..-1])
|
540
|
+
return potentialCountryCode
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
return 0
|
546
|
+
|
547
|
+
end
|
548
|
+
|
549
|
+
|
550
|
+
# Strips any national prefix (such as 0, 1) present in the number provided.
|
551
|
+
#
|
552
|
+
# @param {!goog.string.StringBuffer} number the normalized telephone number
|
553
|
+
# that we wish to strip any national dialing prefix from.
|
554
|
+
# @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
|
555
|
+
# country that we think this number is from.
|
556
|
+
# @return {string} the carrier code extracted if it is present, otherwise
|
557
|
+
# return an empty string.
|
558
|
+
def maybeStripNationalPrefixAndCarrierCode(number, metadata, carrierCode)
|
559
|
+
|
560
|
+
numberStr = number.dup
|
561
|
+
|
562
|
+
possibleNationalPrefix = metadata.national_prefix_for_parsing
|
563
|
+
|
564
|
+
if numberStr == '' || possibleNationalPrefix.blank?
|
565
|
+
# Early return for numbers of zero length.
|
566
|
+
return false
|
567
|
+
end
|
568
|
+
|
569
|
+
# Attempt to parse the first digits as a national prefix.
|
570
|
+
prefixPattern = Regexp.new('\A(?:' + possibleNationalPrefix + ')')
|
571
|
+
|
572
|
+
prefixMatcher = numberStr.match(prefixPattern) # nil or MatchData instance (same behavior as Array)
|
573
|
+
|
574
|
+
if (prefixMatcher)
|
575
|
+
|
576
|
+
nationalNumberRule = Regexp.new(metadata.general_desc.national_number_pattern)
|
577
|
+
|
578
|
+
# prefixMatcher[numOfGroups] == nil implies nothing was captured by the
|
579
|
+
# capturing groups in possibleNationalPrefix; therefore, no transformation
|
580
|
+
# is necessary, and we just remove the national prefix.
|
581
|
+
numOfGroups = prefixMatcher.length - 1
|
582
|
+
|
583
|
+
transformRule = metadata.national_prefix_transform_rule
|
584
|
+
|
585
|
+
noTransform = transformRule.blank? || prefixMatcher[numOfGroups].nil? || prefixMatcher[numOfGroups].length == 0
|
586
|
+
|
587
|
+
# Juste remove the national prefix
|
588
|
+
if noTransform
|
589
|
+
transformedNumber = numberStr[(prefixMatcher[0].length)..-1]
|
590
|
+
|
591
|
+
# Apply the transformRule
|
592
|
+
else
|
593
|
+
transformedNumber = numberStr.sub(prefixPattern, transformRule)
|
594
|
+
end
|
595
|
+
|
596
|
+
# If the original number was viable, and the resultant number is not,
|
597
|
+
# we return.
|
598
|
+
if matchesEntirely_(nationalNumberRule, numberStr) && !matchesEntirely_(nationalNumberRule, transformedNumber)
|
599
|
+
return false
|
600
|
+
end
|
601
|
+
|
602
|
+
if (noTransform && numOfGroups > 0 && !prefixMatcher[1].nil?) ||
|
603
|
+
(!noTransform && numOfGroups > 1)
|
604
|
+
|
605
|
+
if !carrierCode.nil?
|
606
|
+
carrierCode += prefixMatcher[1]
|
607
|
+
end
|
608
|
+
end
|
609
|
+
|
610
|
+
number.replace(transformedNumber)
|
611
|
+
return true
|
612
|
+
end
|
613
|
+
|
614
|
+
return false
|
615
|
+
|
616
|
+
end
|
617
|
+
|
618
|
+
|
619
|
+
# Returns the region code that matches the specific country code. In the case
|
620
|
+
# of no region code being found, ZZ will be returned. In the case of multiple
|
621
|
+
# regions, the one designated in the metadata as the "main" country for this
|
622
|
+
# calling code will be returned.
|
623
|
+
#
|
624
|
+
# @param {number} countryCode the country calling code.
|
625
|
+
# @return {string}
|
626
|
+
def getRegionCodeForCountryCode(countryCode)
|
627
|
+
|
628
|
+
regionCodes = I18nPhoneNumbers::Metadata::countryCodeToRegionCodeMap[countryCode]
|
629
|
+
|
630
|
+
return regionCodes.nil? ? UNKNOWN_REGION_ : regionCodes[0]
|
631
|
+
|
632
|
+
end
|
633
|
+
|
634
|
+
|
635
|
+
# @param {?string} regionCode
|
636
|
+
# @return {i18n.phonenumbers.PhoneMetadata}
|
637
|
+
def getMetadataForRegion(regionCode)
|
638
|
+
|
639
|
+
return regionCode.nil? ? nil : I18nPhoneNumbers::Metadata::countryToMetadata[regionCode.upcase]
|
640
|
+
|
641
|
+
end
|
642
|
+
|
643
|
+
|
644
|
+
# Gets the type of a phone number.
|
645
|
+
#
|
646
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
|
647
|
+
# to know the type.
|
648
|
+
# @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number.
|
649
|
+
def getNumberType(number)
|
650
|
+
|
651
|
+
regionCode = getRegionCodeForNumber(number)
|
652
|
+
|
653
|
+
if !isValidRegionCode_(regionCode) && REGION_CODE_FOR_NON_GEO_ENTITY != regionCode
|
654
|
+
return I18nPhoneNumbers::PhoneNumberType::UNKNOWN
|
655
|
+
end
|
656
|
+
|
657
|
+
nationalSignificantNumber = getNationalSignificantNumber(number)
|
658
|
+
|
659
|
+
return getNumberTypeHelper_(nationalSignificantNumber, getMetadataForRegion(regionCode))
|
660
|
+
|
661
|
+
end
|
662
|
+
|
663
|
+
|
664
|
+
# Gets the type of a phone number.
|
665
|
+
#
|
666
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
|
667
|
+
# to know the type.
|
668
|
+
# @return {boolean}
|
669
|
+
def isMobile?(number)
|
670
|
+
return [I18nPhoneNumbers::PhoneNumberType::MOBILE,
|
671
|
+
I18nPhoneNumbers::PhoneNumberType::FIXED_LINE_OR_MOBILE].include?(getNumberType(number))
|
672
|
+
end
|
673
|
+
|
674
|
+
|
675
|
+
# Returns the country/region where a phone number is from. This could be used
|
676
|
+
# for geo-coding in the country/region level.
|
677
|
+
#
|
678
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin
|
679
|
+
# we want to know.
|
680
|
+
# @return {?string} the country/region where the phone number is from, or null
|
681
|
+
# if no country matches this calling code.
|
682
|
+
def getRegionCodeForNumber(number)
|
683
|
+
|
684
|
+
return nil if number.nil?
|
685
|
+
|
686
|
+
countryCode = number.country_code || 0
|
687
|
+
|
688
|
+
regions = I18nPhoneNumbers::Metadata::countryCodeToRegionCodeMap[countryCode]
|
689
|
+
|
690
|
+
return nil if regions.nil?
|
691
|
+
|
692
|
+
if regions.length == 1
|
693
|
+
return regions[0]
|
694
|
+
else
|
695
|
+
return getRegionCodeForNumberFromRegionList_(number, regions)
|
696
|
+
end
|
697
|
+
|
698
|
+
end
|
699
|
+
|
700
|
+
|
701
|
+
# @param {i18n.phonenumbers.PhoneNumber} number
|
702
|
+
# @param {Array.<string>} regionCodes
|
703
|
+
# @return {?string}
|
704
|
+
# @private
|
705
|
+
def getRegionCodeForNumberFromRegionList_(number, regionCodes)
|
706
|
+
|
707
|
+
nationalNumber = number.national_number.to_s
|
708
|
+
|
709
|
+
regionCodes.each { |regionCode|
|
710
|
+
|
711
|
+
# If leadingDigits is present, use this. Otherwise, do full validation.
|
712
|
+
metadata = getMetadataForRegion(regionCode)
|
713
|
+
|
714
|
+
if !metadata.leading_digits.blank?
|
715
|
+
|
716
|
+
return regionCode if !!nationalNumber.match(Regexp.compile('\A' + metadata.leading_digits)) # starts with leading_digits pattern
|
717
|
+
|
718
|
+
elsif getNumberTypeHelper_(nationalNumber, metadata) != I18nPhoneNumbers::PhoneNumberType::UNKNOWN
|
719
|
+
|
720
|
+
return regionCode
|
721
|
+
|
722
|
+
end
|
723
|
+
|
724
|
+
}
|
725
|
+
|
726
|
+
return nil
|
727
|
+
|
728
|
+
end
|
729
|
+
|
730
|
+
|
731
|
+
# @param {string} nationalNumber
|
732
|
+
# @param {i18n.phonenumbers.PhoneMetadata} metadata
|
733
|
+
# @return {i18n.phonenumbers.PhoneNumberType}
|
734
|
+
# @private
|
735
|
+
def getNumberTypeHelper_(nationalNumber, metadata)
|
736
|
+
|
737
|
+
generalNumberDesc = metadata.general_desc
|
738
|
+
|
739
|
+
if generalNumberDesc.national_number_pattern.nil? || !isNumberMatchingDesc_(nationalNumber, generalNumberDesc)
|
740
|
+
return I18nPhoneNumbers::PhoneNumberType::UNKNOWN
|
741
|
+
end
|
742
|
+
|
743
|
+
# TODO : init and use types different from MOBILE and FIXED
|
744
|
+
|
745
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.premium_rate)
|
746
|
+
# return I18nPhoneNumbers::PhoneNumberType::PREMIUM_RATE
|
747
|
+
# end
|
748
|
+
#
|
749
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.toll_free)
|
750
|
+
# return I18nPhoneNumbers::PhoneNumberType::TOLL_FREE
|
751
|
+
# end
|
752
|
+
#
|
753
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.shared_cost)
|
754
|
+
# return I18nPhoneNumbers::PhoneNumberType::SHARED_COST
|
755
|
+
# end
|
756
|
+
#
|
757
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.voip)
|
758
|
+
# return I18nPhoneNumbers::PhoneNumberType::VOIP
|
759
|
+
# end
|
760
|
+
#
|
761
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.personal_number)
|
762
|
+
# return I18nPhoneNumbers::PhoneNumberType::PERSONAL_NUMBER
|
763
|
+
# end
|
764
|
+
#
|
765
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.pager)
|
766
|
+
# return I18nPhoneNumbers::PhoneNumberType::PAGER
|
767
|
+
# end
|
768
|
+
#
|
769
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.uan)
|
770
|
+
# return I18nPhoneNumbers::PhoneNumberType::UAN
|
771
|
+
# end
|
772
|
+
#
|
773
|
+
# if isNumberMatchingDesc_(nationalNumber, metadata.voicemail)
|
774
|
+
# return I18nPhoneNumbers::PhoneNumberType::VOICEMAIL
|
775
|
+
# end
|
776
|
+
|
777
|
+
isFixedLine = isNumberMatchingDesc_(nationalNumber, metadata.fixed_line)
|
778
|
+
|
779
|
+
if isFixedLine
|
780
|
+
|
781
|
+
if metadata.same_mobile_and_fixed_line_pattern
|
782
|
+
return I18nPhoneNumbers::PhoneNumberType::FIXED_LINE_OR_MOBILE
|
783
|
+
|
784
|
+
elsif isNumberMatchingDesc_(nationalNumber, metadata.mobile)
|
785
|
+
return I18nPhoneNumbers::PhoneNumberType::FIXED_LINE_OR_MOBILE
|
786
|
+
end
|
787
|
+
|
788
|
+
return I18nPhoneNumbers::PhoneNumberType::FIXED_LINE
|
789
|
+
|
790
|
+
end
|
791
|
+
|
792
|
+
# Otherwise, test to see if the number is mobile. Only do this if certain
|
793
|
+
# that the patterns for mobile and fixed line aren't the same.
|
794
|
+
if !metadata.same_mobile_and_fixed_line_pattern && isNumberMatchingDesc_(nationalNumber, metadata.mobile)
|
795
|
+
return I18nPhoneNumbers::PhoneNumberType::MOBILE
|
796
|
+
end
|
797
|
+
|
798
|
+
return I18nPhoneNumbers::PhoneNumberType::UNKNOWN
|
799
|
+
end
|
800
|
+
|
801
|
+
|
802
|
+
# @param {string} nationalNumber
|
803
|
+
# @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
|
804
|
+
# @return {boolean}
|
805
|
+
# @private
|
806
|
+
def isNumberMatchingDesc_(nationalNumber, numberDesc)
|
807
|
+
|
808
|
+
return matchesEntirely_(numberDesc.possible_number_pattern, nationalNumber) &&
|
809
|
+
matchesEntirely_(numberDesc.national_number_pattern, nationalNumber)
|
810
|
+
|
811
|
+
end
|
812
|
+
|
813
|
+
|
814
|
+
# Helper function to check region code is not unknown or null.
|
815
|
+
#
|
816
|
+
# @param {?string} regionCode the ISO 3166-1 two-letter country code that
|
817
|
+
# denotes the country/region that we want to get the country code for.
|
818
|
+
# @return {boolean} true if region code is valid.
|
819
|
+
# @private
|
820
|
+
def isValidRegionCode_(regionCode)
|
821
|
+
|
822
|
+
return !regionCode.nil? &&
|
823
|
+
regionCode != REGION_CODE_FOR_NON_GEO_ENTITY &&
|
824
|
+
I18nPhoneNumbers::Metadata::countryToMetadata.has_key?(regionCode.upcase)
|
825
|
+
|
826
|
+
end
|
827
|
+
|
828
|
+
|
829
|
+
# Gets the national significant number of the a phone number. Note a national
|
830
|
+
# significant number doesn't contain a national prefix or any formatting.
|
831
|
+
#
|
832
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
|
833
|
+
# which the national significant number is needed.
|
834
|
+
# @return {string} the national significant number of the PhoneNumber object
|
835
|
+
# passed in.
|
836
|
+
def getNationalSignificantNumber(number)
|
837
|
+
|
838
|
+
# If a leading zero has been set, we prefix this now. Note this is not a
|
839
|
+
# national prefix.
|
840
|
+
nationalNumber = number.national_number.to_s
|
841
|
+
|
842
|
+
if number.italian_leading_zero
|
843
|
+
return '0' + nationalNumber
|
844
|
+
end
|
845
|
+
|
846
|
+
return nationalNumber
|
847
|
+
|
848
|
+
end
|
849
|
+
|
850
|
+
|
851
|
+
# Attempts to extract a possible number from the string passed in. This
|
852
|
+
# currently strips all leading characters that could not be used to start a
|
853
|
+
# phone number. Characters that can be used to start a phone number are defined
|
854
|
+
# in the VALID_START_CHAR_PATTERN. If none of these characters are found in the
|
855
|
+
# number passed in, an empty string is returned. This function also attempts to
|
856
|
+
# strip off any alternative extensions or endings if two or more are present,
|
857
|
+
# such as in the case of: (530) 583-6985 x302/x2303. The second extension here
|
858
|
+
# makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985
|
859
|
+
# x2303. We remove the second extension so that the first number is parsed
|
860
|
+
# correctly.
|
861
|
+
#
|
862
|
+
# @param {string} number the string that might contain a phone number.
|
863
|
+
# @return {string} the number, stripped of any non-phone-number prefix (such as
|
864
|
+
# 'Tel:') or an empty string if no character used to start phone numbers
|
865
|
+
# (such as + or any digit) is found in the number.
|
866
|
+
#
|
867
|
+
def extractPossibleNumber(number)
|
868
|
+
|
869
|
+
start = number.index(VALID_START_CHAR_PATTERN)
|
870
|
+
|
871
|
+
if !start.nil?
|
872
|
+
|
873
|
+
possibleNumber = number[start..-1]
|
874
|
+
|
875
|
+
# Remove trailing non-alpha non-numerical characters.
|
876
|
+
possibleNumber = possibleNumber.gsub(UNWANTED_END_CHAR_PATTERN_, '')
|
877
|
+
|
878
|
+
# Check for extra numbers at the end.
|
879
|
+
secondNumberStart = possibleNumber.index(SECOND_NUMBER_START_PATTERN_)
|
880
|
+
|
881
|
+
if !secondNumberStart.nil?
|
882
|
+
possibleNumber = possibleNumber[0..secondNumberStart]
|
883
|
+
end
|
884
|
+
|
885
|
+
else
|
886
|
+
possibleNumber = ''
|
887
|
+
end
|
888
|
+
|
889
|
+
return possibleNumber
|
890
|
+
|
891
|
+
end
|
892
|
+
|
893
|
+
|
894
|
+
# Checks to see if the string of characters could possibly be a phone number at
|
895
|
+
# all. At the moment, checks to see that the string begins with at least 3
|
896
|
+
# digits, ignoring any punctuation commonly found in phone numbers. This method
|
897
|
+
# does not require the number to be normalized in advance - but does assume
|
898
|
+
# that leading non-number symbols have been removed, such as by the method
|
899
|
+
# extractPossibleNumber.
|
900
|
+
#
|
901
|
+
# @param {string} number string to be checked for viability as a phone number.
|
902
|
+
# @return {boolean} true if the number could be a phone number of some sort,
|
903
|
+
# otherwise false.
|
904
|
+
def isViablePhoneNumber(number)
|
905
|
+
|
906
|
+
return false if (number.length < MIN_LENGTH_FOR_NSN_)
|
907
|
+
|
908
|
+
return matchesEntirely_(VALID_PHONE_NUMBER_PATTERN_, number)
|
909
|
+
|
910
|
+
end
|
911
|
+
|
912
|
+
|
913
|
+
# Tests whether a phone number matches a valid pattern. Note this doesn't
|
914
|
+
# verify the number is actually in use, which is impossible to tell by just
|
915
|
+
# looking at a number itself.
|
916
|
+
#
|
917
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
|
918
|
+
# to validate.
|
919
|
+
# @return {boolean} a boolean that indicates whether the number is of a valid
|
920
|
+
# pattern.
|
921
|
+
def isValidNumber(number)
|
922
|
+
|
923
|
+
regionCode = getRegionCodeForNumber(number)
|
924
|
+
|
925
|
+
return isValidRegionCode_(regionCode) && isValidNumberForRegion(number, regionCode)
|
926
|
+
end
|
927
|
+
|
928
|
+
|
929
|
+
# Tests whether a phone number is valid for a certain region. Note this doesn't
|
930
|
+
# verify the number is actually in use, which is impossible to tell by just
|
931
|
+
# looking at a number itself. If the country code is not the same as the
|
932
|
+
# country code for the region, this immediately exits with false. After this,
|
933
|
+
# the specific number pattern rules for the region are examined. This is useful
|
934
|
+
# for determining for example whether a particular number is valid for Canada,
|
935
|
+
# rather than just a valid NANPA number.
|
936
|
+
#
|
937
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
|
938
|
+
# to validate.
|
939
|
+
# @param {string} regionCode the ISO 3166-1 two-letter country code that
|
940
|
+
# denotes the region/country that we want to validate the phone number for.
|
941
|
+
# @return {boolean} a boolean that indicates whether the number is of a valid
|
942
|
+
# pattern.
|
943
|
+
def isValidNumberForRegion(number, regionCode)
|
944
|
+
|
945
|
+
return false if (number.country_code || 0) != getCountryCodeForRegion(regionCode)
|
946
|
+
|
947
|
+
metadata = getMetadataForRegion(regionCode)
|
948
|
+
|
949
|
+
generalNumDesc = metadata.general_desc
|
950
|
+
|
951
|
+
nationalSignificantNumber = getNationalSignificantNumber(number)
|
952
|
+
|
953
|
+
# For countries where we don't have metadata for PhoneNumberDesc, we treat
|
954
|
+
# any number passed in as a valid number if its national significant number
|
955
|
+
# is between the minimum and maximum lengths defined by ITU for a national
|
956
|
+
# significant number.
|
957
|
+
if generalNumDesc.national_number_pattern.blank?
|
958
|
+
|
959
|
+
numberLength = nationalSignificantNumber.length
|
960
|
+
|
961
|
+
return numberLength > MIN_LENGTH_FOR_NSN_ && numberLength <= MAX_LENGTH_FOR_NSN_
|
962
|
+
end
|
963
|
+
|
964
|
+
return getNumberTypeHelper_(nationalSignificantNumber, metadata) != I18nPhoneNumbers::PhoneNumberType::UNKNOWN
|
965
|
+
end
|
966
|
+
|
967
|
+
|
968
|
+
# Returns the country calling code for a specific region. For example, this
|
969
|
+
# would be 1 for the United States, and 64 for New Zealand.
|
970
|
+
#
|
971
|
+
# @param {?string} regionCode the ISO 3166-1 two-letter country code that
|
972
|
+
# denotes the country/region that we want to get the country code for.
|
973
|
+
# @return {number} the country calling code for the country/region denoted by
|
974
|
+
# regionCode.
|
975
|
+
def getCountryCodeForRegion(regionCode)
|
976
|
+
|
977
|
+
return 0 if !isValidRegionCode_(regionCode)
|
978
|
+
|
979
|
+
metadata = getMetadataForRegion(regionCode)
|
980
|
+
return 0 if metadata == nil
|
981
|
+
|
982
|
+
return metadata.country_code || 0
|
983
|
+
end
|
984
|
+
|
985
|
+
|
986
|
+
|
987
|
+
# Formats a phone number in the specified format using default rules. Note that
|
988
|
+
# this does not promise to produce a phone number that the user can dial from
|
989
|
+
# where they are - although we do format in either 'national' or
|
990
|
+
# 'international' format depending on what the client asks for, we do not
|
991
|
+
# currently support a more abbreviated format, such as for users in the same
|
992
|
+
# "area" who could potentially dial the number without area code. Note that if
|
993
|
+
# the phone number has a country code of 0 or an otherwise invalid country
|
994
|
+
# code, we cannot work out which formatting rules to apply so we return the
|
995
|
+
# national significant number with no formatting applied.
|
996
|
+
#
|
997
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
|
998
|
+
# formatted.
|
999
|
+
# @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
|
1000
|
+
# phone number should be formatted into.
|
1001
|
+
# @return {string} the formatted phone number.
|
1002
|
+
def format(number, numberFormat)
|
1003
|
+
|
1004
|
+
countryCallingCode = number.country_code || 0
|
1005
|
+
|
1006
|
+
nationalSignificantNumber = getNationalSignificantNumber(number)
|
1007
|
+
|
1008
|
+
if (numberFormat == I18nPhoneNumbers::PhoneNumberFormat::E164)
|
1009
|
+
|
1010
|
+
# Early exit for E164 case since no formatting of the national number needs
|
1011
|
+
# to be applied. Extensions are not formatted.
|
1012
|
+
return formatNumberByFormat_(countryCallingCode, I18nPhoneNumbers::PhoneNumberFormat::E164, nationalSignificantNumber, '')
|
1013
|
+
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
# Note getRegionCodeForCountryCode() is used because formatting information
|
1017
|
+
# for countries which share a country code is contained by only one country
|
1018
|
+
# for performance reasons. For example, for NANPA countries it will be
|
1019
|
+
# contained in the metadata for US.
|
1020
|
+
regionCode = getRegionCodeForCountryCode(countryCallingCode)
|
1021
|
+
|
1022
|
+
return nationalSignificantNumber if !isValidRegionCode_(regionCode)
|
1023
|
+
|
1024
|
+
metadata = getMetadataForRegion(regionCode)
|
1025
|
+
|
1026
|
+
formattedExtension = maybeGetFormattedExtension_(number, regionCode)
|
1027
|
+
|
1028
|
+
formattedNationalNumber = formatNationalNumber_(nationalSignificantNumber, metadata, numberFormat)
|
1029
|
+
|
1030
|
+
return formatNumberByFormat_(countryCallingCode, numberFormat, formattedNationalNumber, formattedExtension)
|
1031
|
+
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
|
1035
|
+
# A helper function that is used by format and formatByPattern.
|
1036
|
+
#
|
1037
|
+
# @param {number} countryCode the country calling code.
|
1038
|
+
# @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
|
1039
|
+
# phone number should be formatted into.
|
1040
|
+
# @param {string} formattedNationalNumber
|
1041
|
+
# @param {string} formattedExtension
|
1042
|
+
# @return {string} the formatted phone number.
|
1043
|
+
# @private
|
1044
|
+
def formatNumberByFormat_(countryCode, numberFormat, formattedNationalNumber, formattedExtension)
|
1045
|
+
|
1046
|
+
pnf = I18nPhoneNumbers::PhoneNumberFormat
|
1047
|
+
|
1048
|
+
res = case numberFormat
|
1049
|
+
when pnf::E164 then PLUS_SIGN + countryCode.to_s + formattedNationalNumber + formattedExtension
|
1050
|
+
when pnf::INTERNATIONAL then PLUS_SIGN + countryCode.to_s + ' ' + formattedNationalNumber + formattedExtension
|
1051
|
+
# "else" is including pnf::NATIONAL
|
1052
|
+
else formattedNationalNumber + formattedExtension
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
return res
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
|
1059
|
+
|
1060
|
+
# Gets the formatted extension of a phone number, if the phone number had an
|
1061
|
+
# extension specified. If not, it returns an empty string.
|
1062
|
+
#
|
1063
|
+
# @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have
|
1064
|
+
# an extension.
|
1065
|
+
# @param {string} regionCode the ISO 3166-1 two-letter country code.
|
1066
|
+
# @return {string} the formatted extension if any.
|
1067
|
+
# @private
|
1068
|
+
def maybeGetFormattedExtension_(number, regionCode)
|
1069
|
+
|
1070
|
+
# NOT SUPPORTED YET
|
1071
|
+
return ''
|
1072
|
+
|
1073
|
+
# if number.extension.blank?
|
1074
|
+
# return ''
|
1075
|
+
# else
|
1076
|
+
# return formatExtension_(number.getExtensionOrDefault(), regionCode)
|
1077
|
+
# end
|
1078
|
+
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
|
1082
|
+
# Note in some countries, the national number can be written in two completely
|
1083
|
+
# different ways depending on whether it forms part of the NATIONAL format or
|
1084
|
+
# INTERNATIONAL format. The numberFormat parameter here is used to specify
|
1085
|
+
# which format to use for those cases. If a carrierCode is specified, this will
|
1086
|
+
# be inserted into the formatted string to replace $CC.
|
1087
|
+
#
|
1088
|
+
# @param {string} number a string of characters representing a phone number.
|
1089
|
+
# @param {i18n.phonenumbers.PhoneMetadata} metadata the metadata for the
|
1090
|
+
# region that we think this number is from.
|
1091
|
+
# @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
|
1092
|
+
# phone number should be formatted into.
|
1093
|
+
# @param {string=} opt_carrierCode
|
1094
|
+
# @return {string} the formatted phone number.
|
1095
|
+
# @private
|
1096
|
+
def formatNationalNumber_(number, metadata, numberFormat, opt_carrierCode = nil)
|
1097
|
+
|
1098
|
+
intlNumberFormats = metadata.intl_number_formats
|
1099
|
+
|
1100
|
+
# When the intlNumberFormats exists, we use that to format national number
|
1101
|
+
# for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
|
1102
|
+
if (intlNumberFormats.length == 0 || numberFormat == I18nPhoneNumbers::PhoneNumberFormat::NATIONAL)
|
1103
|
+
availableFormats = metadata.number_formats
|
1104
|
+
else
|
1105
|
+
availableFormats = metadata.intl_number_formats
|
1106
|
+
end
|
1107
|
+
|
1108
|
+
return formatAccordingToFormats_(number, availableFormats, numberFormat, opt_carrierCode)
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
|
1112
|
+
# Note that carrierCode is optional - if nil or an empty string, no carrier
|
1113
|
+
# code replacement will take place.
|
1114
|
+
#
|
1115
|
+
# @param {string} nationalNumber a string of characters representing a phone
|
1116
|
+
# number.
|
1117
|
+
# @param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the
|
1118
|
+
# available formats the phone number could be formatted into.
|
1119
|
+
# @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
|
1120
|
+
# phone number should be formatted into.
|
1121
|
+
# @param {string=} opt_carrierCode
|
1122
|
+
# @return {string} the formatted phone number.
|
1123
|
+
# @private
|
1124
|
+
def formatAccordingToFormats_(nationalNumber, availableFormats, numberFormat, opt_carrierCode = nil)
|
1125
|
+
|
1126
|
+
availableFormats.each do |numFormat|
|
1127
|
+
|
1128
|
+
ldp = numFormat.leading_digits_patterns
|
1129
|
+
|
1130
|
+
# We always use the last leading_digits_pattern, as it is the most detailed.
|
1131
|
+
if ldp.empty? || nationalNumber.index(Regexp.compile(ldp[-1])) == 0
|
1132
|
+
|
1133
|
+
patternToMatch = Regexp.new(numFormat.pattern)
|
1134
|
+
|
1135
|
+
if matchesEntirely_(patternToMatch, nationalNumber)
|
1136
|
+
|
1137
|
+
numberFormatRule = numFormat.format
|
1138
|
+
|
1139
|
+
domesticCarrierCodeFormattingRule = numFormat.carrier_code_formatting_rule
|
1140
|
+
|
1141
|
+
if (numberFormat == I18nPhoneNumbers::PhoneNumberFormat::NATIONAL &&
|
1142
|
+
!opt_carrierCode.blank? &&
|
1143
|
+
!domesticCarrierCodeFormattingRule.blank?)
|
1144
|
+
|
1145
|
+
# Replace the $CC in the formatting rule with the desired carrier code.
|
1146
|
+
carrierCodeFormattingRule = domesticCarrierCodeFormattingRule.sub(CC_PATTERN_, opt_carrierCode)
|
1147
|
+
|
1148
|
+
# Now replace the $FG in the formatting rule with the first group
|
1149
|
+
# and the carrier code combined in the appropriate way.
|
1150
|
+
numberFormatRule = numberFormatRule.sub(FIRST_GROUP_PATTERN_, carrierCodeFormattingRule)
|
1151
|
+
|
1152
|
+
return nationalNumber.sub(patternToMatch, numberFormatRule)
|
1153
|
+
|
1154
|
+
else
|
1155
|
+
# Use the national prefix formatting rule instead.
|
1156
|
+
nationalPrefixFormattingRule = numFormat.national_prefix_formatting_rule
|
1157
|
+
|
1158
|
+
if (numberFormat == I18nPhoneNumbers::PhoneNumberFormat::NATIONAL &&
|
1159
|
+
!nationalPrefixFormattingRule.blank?)
|
1160
|
+
return nationalNumber.sub(patternToMatch, numberFormatRule.sub(FIRST_GROUP_PATTERN_, nationalPrefixFormattingRule))
|
1161
|
+
else
|
1162
|
+
return nationalNumber.sub(patternToMatch, numberFormatRule)
|
1163
|
+
end
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
end
|
1167
|
+
end
|
1168
|
+
|
1169
|
+
# If no pattern above is matched, we format the number as a whole.
|
1170
|
+
return nationalNumber
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
|
1174
|
+
|
1175
|
+
# Gets a valid number for the specified country.
|
1176
|
+
#
|
1177
|
+
# @param {string} regionCode the ISO 3166-1 two-letter country code that
|
1178
|
+
# denotes the country for which an example number is needed.
|
1179
|
+
# @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the
|
1180
|
+
# specified country. Returns null when the metadata does not contain such
|
1181
|
+
# information.
|
1182
|
+
def getExampleNumber(regionCode)
|
1183
|
+
return getExampleNumberForType(regionCode, I18nPhoneNumbers::PhoneNumberType::FIXED_LINE)
|
1184
|
+
end
|
1185
|
+
|
1186
|
+
|
1187
|
+
# Gets a valid number, if any, for the specified country and number type.
|
1188
|
+
#
|
1189
|
+
# @param {string} regionCode the ISO 3166-1 two-letter country code that
|
1190
|
+
# denotes the country for which an example number is needed.
|
1191
|
+
# @param {i18n.phonenumbers.PhoneNumberType} type the type of number that is
|
1192
|
+
# needed.
|
1193
|
+
# @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified
|
1194
|
+
# country and type. Returns null when the metadata does not contain such
|
1195
|
+
# information.
|
1196
|
+
def getExampleNumberForType(regionCode, type)
|
1197
|
+
|
1198
|
+
desc = getNumberDescByType_(getMetadataForRegion(regionCode), type)
|
1199
|
+
|
1200
|
+
begin
|
1201
|
+
if !desc.example_number.blank?
|
1202
|
+
return parse(desc.example_number, regionCode)
|
1203
|
+
end
|
1204
|
+
rescue
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
return nil
|
1208
|
+
end
|
1209
|
+
|
1210
|
+
|
1211
|
+
#
|
1212
|
+
# @param {i18n.phonenumbers.PhoneMetadata} metadata
|
1213
|
+
# @param {i18n.phonenumbers.PhoneNumberType} type
|
1214
|
+
# @return {i18n.phonenumbers.PhoneNumberDesc}
|
1215
|
+
# @private
|
1216
|
+
def getNumberDescByType_(metadata, type)
|
1217
|
+
|
1218
|
+
case type
|
1219
|
+
# when I18nPhoneNumbers::PhoneNumberType::PREMIUM_RATE then metadata.premium_rate
|
1220
|
+
# when I18nPhoneNumbers::PhoneNumberType::TOLL_FREE then metadata.toll_free
|
1221
|
+
when I18nPhoneNumbers::PhoneNumberType::MOBILE then metadata.mobile
|
1222
|
+
when I18nPhoneNumbers::PhoneNumberType::FIXED_LINE, I18nPhoneNumbers::PhoneNumberType::FIXED_LINE_OR_MOBILE then metadata.fixed_line
|
1223
|
+
# when I18nPhoneNumbers::PhoneNumberType::SHARED_COST then metadata.shared_cost
|
1224
|
+
# when I18nPhoneNumbers::PhoneNumberType::VOIP then metadata.voip
|
1225
|
+
# when I18nPhoneNumbers::PhoneNumberType::PERSONAL_NUMBER then metadata.personal_number
|
1226
|
+
# when I18nPhoneNumbers::PhoneNumberType::PAGER then metadata.pager
|
1227
|
+
# when I18nPhoneNumbers::PhoneNumberType::UAN then metadata.uan
|
1228
|
+
else metadata.general_desc
|
1229
|
+
end
|
1230
|
+
|
1231
|
+
end
|
1232
|
+
|
1233
|
+
end # end of class << self
|
1234
|
+
|
1235
|
+
end # end of Util module
|
1236
|
+
|
1237
|
+
end
|