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.
@@ -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