JS: libphonenumber 4.5
[platform/upstream/libphonenumber.git] / javascript / i18n / phonenumbers / asyoutypeformatter.js
1 /**
2  * @license
3  * Copyright (C) 2010 The Libphonenumber Authors
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 /**
19  * @fileoverview  A formatter which formats phone numbers as they are entered.
20  * (based on the java implementation).
21  *
22  * <p>An AsYouTypeFormatter can be created by new AsYouTypeFormatter(). After
23  * that, digits can be added by invoking {@link #inputDigit} on the formatter
24  * instance, and the partially formatted phone number will be returned each time
25  * a digit is added. {@link #clear} can be invoked before formatting a new
26  * number.
27  *
28  * <p>See the unittests for more details on how the formatter is to be used.
29  *
30  * @author Nikolaos Trogkanis
31  */
32
33 goog.provide('i18n.phonenumbers.AsYouTypeFormatter');
34
35 goog.require('goog.string.StringBuffer');
36 goog.require('i18n.phonenumbers.NumberFormat');
37 goog.require('i18n.phonenumbers.PhoneMetadata');
38 goog.require('i18n.phonenumbers.PhoneMetadataCollection');
39 goog.require('i18n.phonenumbers.PhoneNumber');
40 goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource');
41 goog.require('i18n.phonenumbers.PhoneNumberDesc');
42 goog.require('i18n.phonenumbers.PhoneNumberUtil');
43 goog.require('i18n.phonenumbers.metadata');
44
45
46
47 /**
48  * Constructs an AsYouTypeFormatter for the specific region.
49  *
50  * @param {string} regionCode the ISO 3166-1 two-letter region code that denotes
51  *     the region where the phone number is being entered.
52  * @constructor
53  */
54 i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
55   /**
56    * The digits that have not been entered yet will be represented by a \u2008,
57    * the punctuation space.
58    * @const
59    * @type {string}
60    * @private
61    */
62   this.digitPlaceholder_ = '\u2008';
63   /**
64    * @type {RegExp}
65    * @private
66    */
67   this.digitPattern_ = new RegExp(this.digitPlaceholder_);
68   /**
69    * @type {string}
70    * @private
71    */
72   this.currentOutput_ = '';
73   /**
74    * @type {!goog.string.StringBuffer}
75    * @private
76    */
77   this.formattingTemplate_ = new goog.string.StringBuffer();
78   /**
79    * The pattern from numberFormat that is currently used to create
80    * formattingTemplate.
81    * @type {string}
82    * @private
83    */
84   this.currentFormattingPattern_ = '';
85   /**
86    * @type {!goog.string.StringBuffer}
87    * @private
88    */
89   this.accruedInput_ = new goog.string.StringBuffer();
90   /**
91    * @type {!goog.string.StringBuffer}
92    * @private
93    */
94   this.accruedInputWithoutFormatting_ = new goog.string.StringBuffer();
95   /**
96    * This indicates whether AsYouTypeFormatter is currently doing the
97    * formatting.
98    * @type {boolean}
99    * @private
100    */
101   this.ableToFormat_ = true;
102   /**
103    * Set to true when users enter their own formatting. AsYouTypeFormatter will
104    * do no formatting at all when this is set to true.
105    * @type {boolean}
106    * @private
107    */
108   this.inputHasFormatting_ = false;
109   /**
110    * @type {boolean}
111    * @private
112    */
113   this.isInternationalFormatting_ = false;
114   /**
115    * @type {boolean}
116    * @private
117    */
118   this.isExpectingCountryCallingCode_ = false;
119   /**
120    * @type {i18n.phonenumbers.PhoneNumberUtil}
121    * @private
122    */
123   this.phoneUtil_ = i18n.phonenumbers.PhoneNumberUtil.getInstance();
124   /**
125    * @type {number}
126    * @private
127    */
128   this.lastMatchPosition_ = 0;
129   /**
130    * The position of a digit upon which inputDigitAndRememberPosition is most
131    * recently invoked, as found in the original sequence of characters the user
132    * entered.
133    * @type {number}
134    * @private
135    */
136   this.originalPosition_ = 0;
137   /**
138    * The position of a digit upon which inputDigitAndRememberPosition is most
139    * recently invoked, as found in accruedInputWithoutFormatting.
140    * entered.
141    * @type {number}
142    * @private
143    */
144   this.positionToRemember_ = 0;
145   /**
146    * This contains anything that has been entered so far preceding the national
147    * significant number, and it is formatted (e.g. with space inserted). For
148    * example, this can contain IDD, country code, and/or NDD, etc.
149    * @type {!goog.string.StringBuffer}
150    * @private
151    */
152   this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
153   /**
154    * This contains the national prefix that has been extracted. It contains only
155    * digits without formatting.
156    * @type {string}
157    * @private
158    */
159   this.nationalPrefixExtracted_ = '';
160   /**
161    * @type {!goog.string.StringBuffer}
162    * @private
163    */
164   this.nationalNumber_ = new goog.string.StringBuffer();
165   /**
166    * @type {Array.<i18n.phonenumbers.NumberFormat>}
167    * @private
168    */
169   this.possibleFormats_ = [];
170   /**
171    * @type {string}
172    * @private
173    */
174   this.defaultCountry_ = regionCode;
175   this.currentMetaData_ = this.getMetadataForRegion_(this.defaultCountry_);
176   /**
177    * @type {i18n.phonenumbers.PhoneMetadata}
178    * @private
179    */
180   this.defaultMetaData_ = this.currentMetaData_;
181 };
182
183
184 /**
185  * @const
186  * @type {i18n.phonenumbers.PhoneMetadata}
187  * @private
188  */
189 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
190     new i18n.phonenumbers.PhoneMetadata();
191 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
192     .setInternationalPrefix('NA');
193
194
195 /**
196  * A pattern that is used to match character classes in regular expressions.
197  * An example of a character class is [1-4].
198  * @const
199  * @type {RegExp}
200  * @private
201  */
202 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
203     /\[([^\[\]])*\]/g;
204
205
206 /**
207  * Any digit in a regular expression that actually denotes a digit. For
208  * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
209  * (8 and 0) are standalone digits, but the rest are not.
210  * Two look-aheads are needed because the number following \\d could be a
211  * two-digit number, since the phone number can be as long as 15 digits.
212  * @const
213  * @type {RegExp}
214  * @private
215  */
216 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
217     /\d(?=[^,}][^,}])/g;
218
219
220 /**
221  * A pattern that is used to determine if a numberFormat under availableFormats
222  * is eligible to be used by the AYTF. It is eligible when the format element
223  * under numberFormat contains groups of the dollar sign followed by a single
224  * digit, separated by valid phone number punctuation. This prevents invalid
225  * punctuation (such as the star sign in Israeli star numbers) getting into the
226  * output of the AYTF.
227  * @const
228  * @type {RegExp}
229  * @private
230  */
231 i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
232     '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
233     '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
234
235
236 /**
237  * This is the minimum length of national number accrued that is required to
238  * trigger the formatter. The first element of the leadingDigitsPattern of
239  * each numberFormat contains a regular expression that matches up to this
240  * number of digits.
241  * @const
242  * @type {number}
243  * @private
244  */
245 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
246
247
248 /**
249  * The metadata needed by this class is the same for all regions sharing the
250  * same country calling code. Therefore, we return the metadata for "main"
251  * region for this country calling code.
252  * @param {string} regionCode
253  * @return {i18n.phonenumbers.PhoneMetadata}
254  * @private
255  */
256 i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
257     function(regionCode) {
258
259   /** @type {number} */
260   var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
261   /** @type {string} */
262   var mainCountry =
263       this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
264   /** @type {i18n.phonenumbers.PhoneMetadata} */
265   var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
266   if (metadata != null) {
267     return metadata;
268   }
269   // Set to a default instance of the metadata. This allows us to function with
270   // an incorrect region code, even if formatting only works for numbers
271   // specified with '+'.
272   return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
273 };
274
275
276 /**
277  * @return {boolean} true if a new template is created as opposed to reusing the
278  *     existing template.
279  * @private
280  */
281 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
282     function() {
283
284   // When there are multiple available formats, the formatter uses the first
285   // format where a formatting template could be created.
286   /** @type {number} */
287   var possibleFormatsLength = this.possibleFormats_.length;
288   for (var i = 0; i < possibleFormatsLength; ++i) {
289     /** @type {i18n.phonenumbers.NumberFormat} */
290     var numberFormat = this.possibleFormats_[i];
291     /** @type {string} */
292     var pattern = numberFormat.getPatternOrDefault();
293     if (this.currentFormattingPattern_ == pattern) {
294       return false;
295     }
296     if (this.createFormattingTemplate_(numberFormat)) {
297       this.currentFormattingPattern_ = pattern;
298       // With a new formatting template, the matched position using the old
299       // template needs to be reset.
300       this.lastMatchPosition_ = 0;
301       return true;
302     }
303   }
304   this.ableToFormat_ = false;
305   return false;
306 };
307
308
309 /**
310  * @param {string} leadingThreeDigits
311  * @private
312  */
313 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
314     function(leadingThreeDigits) {
315
316   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
317   var formatList =
318       (this.isInternationalFormatting_ &&
319            this.currentMetaData_.intlNumberFormatCount() > 0) ?
320       this.currentMetaData_.intlNumberFormatArray() :
321       this.currentMetaData_.numberFormatArray();
322   /** @type {number} */
323   var formatListLength = formatList.length;
324   for (var i = 0; i < formatListLength; ++i) {
325     /** @type {i18n.phonenumbers.NumberFormat} */
326     var format = formatList[i];
327     if (this.isFormatEligible_(format.getFormatOrDefault())) {
328       this.possibleFormats_.push(format);
329     }
330   }
331   this.narrowDownPossibleFormats_(leadingThreeDigits);
332 };
333
334
335 /**
336  * @param {string} format
337  * @return {boolean}
338  * @private
339  */
340 i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
341     function(format) {
342   return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
343       .test(format);
344 };
345
346
347 /**
348  * @param {string} leadingDigits
349  * @private
350  */
351 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
352     function(leadingDigits) {
353
354   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
355   var possibleFormats = [];
356   /** @type {number} */
357   var indexOfLeadingDigitsPattern =
358       leadingDigits.length -
359       i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
360   /** @type {number} */
361   var possibleFormatsLength = this.possibleFormats_.length;
362   for (var i = 0; i < possibleFormatsLength; ++i) {
363     /** @type {i18n.phonenumbers.NumberFormat} */
364     var format = this.possibleFormats_[i];
365     if (format.leadingDigitsPatternCount() > indexOfLeadingDigitsPattern) {
366       /** @type {string} */
367       var leadingDigitsPattern =
368           format.getLeadingDigitsPatternOrDefault(indexOfLeadingDigitsPattern);
369       if (leadingDigits.search(leadingDigitsPattern) == 0) {
370         possibleFormats.push(this.possibleFormats_[i]);
371       }
372     } else {
373       // else the particular format has no more specific leadingDigitsPattern,
374       // and it should be retained.
375       possibleFormats.push(this.possibleFormats_[i]);
376     }
377   }
378   this.possibleFormats_ = possibleFormats;
379 };
380
381
382 /**
383  * @param {i18n.phonenumbers.NumberFormat} format
384  * @return {boolean}
385  * @private
386  */
387 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
388     function(format) {
389
390   /** @type {string} */
391   var numberPattern = format.getPatternOrDefault();
392
393   // The formatter doesn't format numbers when numberPattern contains '|', e.g.
394   // (20|3)\d{4}. In those cases we quickly return.
395   if (numberPattern.indexOf('|') != -1) {
396     return false;
397   }
398
399   // Replace anything in the form of [..] with \d
400   numberPattern = numberPattern.replace(
401       i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
402
403   // Replace any standalone digit (not the one in d{}) with \d
404   numberPattern = numberPattern.replace(
405       i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
406   this.formattingTemplate_.clear();
407   /** @type {string} */
408   var tempTemplate = this.getFormattingTemplate_(numberPattern,
409                                                  format.getFormatOrDefault());
410   if (tempTemplate.length > 0) {
411     this.formattingTemplate_.append(tempTemplate);
412     return true;
413   }
414   return false;
415 };
416
417
418 /**
419  * Gets a formatting template which can be used to efficiently format a
420  * partial number where digits are added one by one.
421  *
422  * @param {string} numberPattern
423  * @param {string} numberFormat
424  * @return {string}
425  * @private
426  */
427 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
428     function(numberPattern, numberFormat) {
429
430   // Creates a phone number consisting only of the digit 9 that matches the
431   // numberPattern by applying the pattern to the longestPhoneNumber string.
432   /** @type {string} */
433   var longestPhoneNumber = '999999999999999';
434   /** @type {Array.<string>} */
435   var m = longestPhoneNumber.match(numberPattern);
436   // this match will always succeed
437   /** @type {string} */
438   var aPhoneNumber = m[0];
439   // No formatting template can be created if the number of digits entered so
440   // far is longer than the maximum the current formatting rule can accommodate.
441   if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
442     return '';
443   }
444   // Formats the number according to numberFormat
445   /** @type {string} */
446   var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
447                                       numberFormat);
448   // Replaces each digit with character digitPlaceholder
449   template = template.replace(new RegExp('9', 'g'), this.digitPlaceholder_);
450   return template;
451 };
452
453
454 /**
455  * Clears the internal state of the formatter, so it can be reused.
456  */
457 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
458   this.currentOutput_ = '';
459   this.accruedInput_.clear();
460   this.accruedInputWithoutFormatting_.clear();
461   this.formattingTemplate_.clear();
462   this.lastMatchPosition_ = 0;
463   this.currentFormattingPattern_ = '';
464   this.prefixBeforeNationalNumber_.clear();
465   this.nationalPrefixExtracted_ = '';
466   this.nationalNumber_.clear();
467   this.ableToFormat_ = true;
468   this.inputHasFormatting_ = false;
469   this.positionToRemember_ = 0;
470   this.originalPosition_ = 0;
471   this.isInternationalFormatting_ = false;
472   this.isExpectingCountryCallingCode_ = false;
473   this.possibleFormats_ = [];
474   if (this.currentMetaData_ != this.defaultMetaData_) {
475     this.currentMetaData_ = this.getMetadataForRegion_(this.defaultCountry_);
476   }
477 };
478
479
480 /**
481  * Formats a phone number on-the-fly as each digit is entered.
482  *
483  * @param {string} nextChar the most recently entered digit of a phone number.
484  *     Formatting characters are allowed, but as soon as they are encountered
485  *     this method formats the number as entered and not 'as you type' anymore.
486  *     Full width digits and Arabic-indic digits are allowed, and will be shown
487  *     as they are.
488  * @return {string} the partially formatted phone number.
489  */
490 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
491   this.currentOutput_ =
492       this.inputDigitWithOptionToRememberPosition_(nextChar, false);
493   return this.currentOutput_;
494 };
495
496
497 /**
498  * Same as {@link #inputDigit}, but remembers the position where
499  * {@code nextChar} is inserted, so that it can be retrieved later by using
500  * {@link #getRememberedPosition}. The remembered position will be automatically
501  * adjusted if additional formatting characters are later inserted/removed in
502  * front of {@code nextChar}.
503  *
504  * @param {string} nextChar
505  * @return {string}
506  */
507 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
508     function(nextChar) {
509
510   this.currentOutput_ =
511       this.inputDigitWithOptionToRememberPosition_(nextChar, true);
512   return this.currentOutput_;
513 };
514
515
516 /**
517  * @param {string} nextChar
518  * @param {boolean} rememberPosition
519  * @return {string}
520  * @private
521  */
522 i18n.phonenumbers.AsYouTypeFormatter.prototype.
523     inputDigitWithOptionToRememberPosition_ = function(nextChar,
524                                                        rememberPosition) {
525
526   this.accruedInput_.append(nextChar);
527   if (rememberPosition) {
528     this.originalPosition_ = this.accruedInput_.getLength();
529   }
530   // We do formatting on-the-fly only when each character entered is either a
531   // digit, or a plus sign (accepted at the start of the number only).
532   if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
533     this.ableToFormat_ = false;
534     this.inputHasFormatting_ = true;
535   } else {
536     nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
537                                                          rememberPosition);
538   }
539   if (!this.ableToFormat_) {
540     // When we are unable to format because of reasons other than that
541     // formatting chars have been entered, it can be due to really long IDDs or
542     // NDDs. If that is the case, we might be able to do formatting again after
543     // extracting them.
544     if (this.inputHasFormatting_) {
545       return this.accruedInput_.toString();
546     } else if (this.attemptToExtractIdd_()) {
547       if (this.attemptToExtractCountryCallingCode_()) {
548         return this.attemptToChoosePatternWithPrefixExtracted_();
549       }
550     } else if (this.ableToExtractLongerNdd_()) {
551       // Add an additional space to separate long NDD and national significant
552       // number for readability.
553       this.prefixBeforeNationalNumber_.append(' ');
554       return this.attemptToChoosePatternWithPrefixExtracted_();
555     }
556     return this.accruedInput_.toString();
557   }
558
559   // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
560   // digits (the plus sign is counted as a digit as well for this purpose) have
561   // been entered.
562   switch (this.accruedInputWithoutFormatting_.getLength()) {
563     case 0:
564     case 1:
565     case 2:
566       return this.accruedInput_.toString();
567     case 3:
568       if (this.attemptToExtractIdd_()) {
569         this.isExpectingCountryCallingCode_ = true;
570       } else {
571         // No IDD or plus sign is found, might be entering in national format.
572         this.nationalPrefixExtracted_ =
573             this.removeNationalPrefixFromNationalNumber_();
574         return this.attemptToChooseFormattingPattern_();
575       }
576     default:
577       if (this.isExpectingCountryCallingCode_) {
578         if (this.attemptToExtractCountryCallingCode_()) {
579           this.isExpectingCountryCallingCode_ = false;
580         }
581         return this.prefixBeforeNationalNumber_.toString() +
582             this.nationalNumber_.toString();
583       }
584       if (this.possibleFormats_.length > 0) {
585         // The formatting pattern is already chosen.
586         /** @type {string} */
587         var tempNationalNumber = this.inputDigitHelper_(nextChar);
588         // See if the accrued digits can be formatted properly already. If not,
589         // use the results from inputDigitHelper, which does formatting based on
590         // the formatting pattern chosen.
591         /** @type {string} */
592         var formattedNumber = this.attemptToFormatAccruedDigits_();
593         if (formattedNumber.length > 0) {
594           return formattedNumber;
595         }
596         this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
597         if (this.maybeCreateNewTemplate_()) {
598           return this.inputAccruedNationalNumber_();
599         }
600         return this.ableToFormat_ ?
601             this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
602             this.accruedInput_.toString();
603       } else {
604         return this.attemptToChooseFormattingPattern_();
605       }
606   }
607 };
608
609
610 /**
611  * @return {string}
612  * @private
613  */
614 i18n.phonenumbers.AsYouTypeFormatter.prototype.
615     attemptToChoosePatternWithPrefixExtracted_ = function() {
616
617   this.ableToFormat_ = true;
618   this.isExpectingCountryCallingCode_ = false;
619   this.possibleFormats_ = [];
620   return this.attemptToChooseFormattingPattern_();
621 };
622
623
624 /**
625  * Some national prefixes are a substring of others. If extracting the shorter
626  * NDD doesn't result in a number we can format, we try to see if we can extract
627  * a longer version here.
628  * @return {boolean}
629  * @private
630  */
631 i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
632     function() {
633   if (this.nationalPrefixExtracted_.length > 0) {
634     // Put the extracted NDD back to the national number before attempting to
635     // extract a new NDD.
636     /** @type {string} */
637     var nationalNumberStr = this.nationalNumber_.toString();
638     this.nationalNumber_.clear();
639     this.nationalNumber_.append(this.nationalPrefixExtracted_);
640     this.nationalNumber_.append(nationalNumberStr);
641     // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
642     // cannot simply set it to empty string because people sometimes enter
643     // national prefix after country code, e.g +44 (0)20-1234-5678.
644     /** @type {string} */
645     var prefixBeforeNationalNumberStr =
646         this.prefixBeforeNationalNumber_.toString();
647     /** @type {number} */
648     var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
649         this.nationalPrefixExtracted_);
650     this.prefixBeforeNationalNumber_.clear();
651     this.prefixBeforeNationalNumber_.append(
652         prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
653   }
654   return this.nationalPrefixExtracted_ !=
655       this.removeNationalPrefixFromNationalNumber_();
656 };
657
658
659 /**
660  * @param {string} nextChar
661  * @return {boolean}
662  * @private
663  */
664 i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
665     function(nextChar) {
666   return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
667       .test(nextChar) ||
668       (this.accruedInput_.getLength() == 1 &&
669        i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
670 };
671
672
673 /**
674  * @return {string}
675  * @private
676  */
677 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
678     function() {
679
680   /** @type {string} */
681   var nationalNumber = this.nationalNumber_.toString();
682   /** @type {number} */
683   var possibleFormatsLength = this.possibleFormats_.length;
684   for (var i = 0; i < possibleFormatsLength; ++i) {
685     /** @type {i18n.phonenumbers.NumberFormat} */
686     var numFormat = this.possibleFormats_[i];
687     /** @type {string} */
688     var pattern = numFormat.getPatternOrDefault();
689     /** @type {RegExp} */
690     var patternRegExp = new RegExp('^(?:' + pattern + ')$');
691     if (patternRegExp.test(nationalNumber)) {
692       /** @type {string} */
693       var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
694                                                    numFormat.getFormat());
695       return this.prefixBeforeNationalNumber_.toString() + formattedNumber;
696     }
697   }
698   return '';
699 };
700
701
702 /**
703  * Returns the current position in the partially formatted phone number of the
704  * character which was previously passed in as the parameter of
705  * {@link #inputDigitAndRememberPosition}.
706  *
707  * @return {number}
708  */
709 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
710     function() {
711
712   if (!this.ableToFormat_) {
713     return this.originalPosition_;
714   }
715   /** @type {number} */
716   var accruedInputIndex = 0;
717   /** @type {number} */
718   var currentOutputIndex = 0;
719   /** @type {string} */
720   var accruedInputWithoutFormatting =
721       this.accruedInputWithoutFormatting_.toString();
722   /** @type {string} */
723   var currentOutput = this.currentOutput_.toString();
724   while (accruedInputIndex < this.positionToRemember_ &&
725          currentOutputIndex < currentOutput.length) {
726     if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
727         currentOutput.charAt(currentOutputIndex)) {
728       accruedInputIndex++;
729     }
730     currentOutputIndex++;
731   }
732   return currentOutputIndex;
733 };
734
735
736 /**
737  * Attempts to set the formatting template and returns a string which contains
738  * the formatted version of the digits entered so far.
739  *
740  * @return {string}
741  * @private
742  */
743 i18n.phonenumbers.AsYouTypeFormatter.prototype.
744     attemptToChooseFormattingPattern_ = function() {
745
746   /** @type {string} */
747   var nationalNumber = this.nationalNumber_.toString();
748   // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
749   // digits of national number (excluding national prefix) have been entered.
750   if (nationalNumber.length >=
751       i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
752     this.getAvailableFormats_(
753         nationalNumber.substring(0,
754             i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_));
755     return this.maybeCreateNewTemplate_() ?
756         this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
757   } else {
758     return this.prefixBeforeNationalNumber_.toString() + nationalNumber;
759   }
760 };
761
762
763 /**
764  * Invokes inputDigitHelper on each digit of the national number accrued, and
765  * returns a formatted string in the end.
766  *
767  * @return {string}
768  * @private
769  */
770 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
771     function() {
772
773   /** @type {string} */
774   var nationalNumber = this.nationalNumber_.toString();
775   /** @type {number} */
776   var lengthOfNationalNumber = nationalNumber.length;
777   if (lengthOfNationalNumber > 0) {
778     /** @type {string} */
779     var tempNationalNumber = '';
780     for (var i = 0; i < lengthOfNationalNumber; i++) {
781       tempNationalNumber =
782           this.inputDigitHelper_(nationalNumber.charAt(i));
783     }
784     return this.ableToFormat_ ?
785         this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
786         this.accruedInput_.toString();
787   } else {
788     return this.prefixBeforeNationalNumber_.toString();
789   }
790 };
791
792
793 /**
794  * Returns the national prefix extracted, or an empty string if it is not
795  * present.
796  * @return {string}
797  * @private
798  */
799 i18n.phonenumbers.AsYouTypeFormatter.prototype.
800     removeNationalPrefixFromNationalNumber_ = function() {
801
802   /** @type {string} */
803   var nationalNumber = this.nationalNumber_.toString();
804   /** @type {number} */
805   var startOfNationalNumber = 0;
806   if (this.currentMetaData_.getCountryCode() == 1 &&
807       nationalNumber.charAt(0) == '1') {
808     startOfNationalNumber = 1;
809     this.prefixBeforeNationalNumber_.append('1 ');
810     this.isInternationalFormatting_ = true;
811   } else if (this.currentMetaData_.hasNationalPrefixForParsing()) {
812     /** @type {RegExp} */
813     var nationalPrefixForParsing = new RegExp(
814         '^(?:' + this.currentMetaData_.getNationalPrefixForParsing() + ')');
815     /** @type {Array.<string>} */
816     var m = nationalNumber.match(nationalPrefixForParsing);
817     if (m != null && m[0] != null && m[0].length > 0) {
818       // When the national prefix is detected, we use international formatting
819       // rules instead of national ones, because national formatting rules could
820       // contain local formatting rules for numbers entered without area code.
821       this.isInternationalFormatting_ = true;
822       startOfNationalNumber = m[0].length;
823       this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
824           startOfNationalNumber));
825     }
826   }
827   this.nationalNumber_.clear();
828   this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
829   return nationalNumber.substring(0, startOfNationalNumber);
830 };
831
832
833 /**
834  * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
835  * available, and places the remaining input into nationalNumber.
836  *
837  * @return {boolean} true when accruedInputWithoutFormatting begins with the
838  *     plus sign or valid IDD for defaultCountry.
839  * @private
840  */
841 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
842     function() {
843
844   /** @type {string} */
845   var accruedInputWithoutFormatting =
846       this.accruedInputWithoutFormatting_.toString();
847   /** @type {RegExp} */
848   var internationalPrefix = new RegExp(
849       '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
850       this.currentMetaData_.getInternationalPrefix() + ')');
851   /** @type {Array.<string>} */
852   var m = accruedInputWithoutFormatting.match(internationalPrefix);
853   if (m != null && m[0] != null && m[0].length > 0) {
854     this.isInternationalFormatting_ = true;
855     /** @type {number} */
856     var startOfCountryCallingCode = m[0].length;
857     this.nationalNumber_.clear();
858     this.nationalNumber_.append(
859         accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
860     this.prefixBeforeNationalNumber_.clear();
861     this.prefixBeforeNationalNumber_.append(
862         accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
863     if (accruedInputWithoutFormatting.charAt(0) !=
864         i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
865       this.prefixBeforeNationalNumber_.append(' ');
866     }
867     return true;
868   }
869   return false;
870 };
871
872
873 /**
874  * Extracts the country calling code from the beginning of nationalNumber to
875  * prefixBeforeNationalNumber when they are available, and places the remaining
876  * input into nationalNumber.
877  *
878  * @return {boolean} true when a valid country calling code can be found.
879  * @private
880  */
881 i18n.phonenumbers.AsYouTypeFormatter.prototype.
882     attemptToExtractCountryCallingCode_ = function() {
883
884   if (this.nationalNumber_.getLength() == 0) {
885     return false;
886   }
887   /** @type {!goog.string.StringBuffer} */
888   var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
889   /** @type {number} */
890   var countryCode = this.phoneUtil_.extractCountryCode(
891       this.nationalNumber_, numberWithoutCountryCallingCode);
892   if (countryCode == 0) {
893     return false;
894   }
895   this.nationalNumber_.clear();
896   this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
897   /** @type {string} */
898   var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
899   if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
900       newRegionCode) {
901     this.currentMetaData_ =
902         this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
903   } else if (newRegionCode != this.defaultCountry_) {
904     this.currentMetaData_ = this.getMetadataForRegion_(newRegionCode);
905   }
906   /** @type {string} */
907   var countryCodeString = '' + countryCode;
908   this.prefixBeforeNationalNumber_.append(countryCodeString).append(' ');
909   return true;
910 };
911
912
913 /**
914  * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
915  * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
916  * version of digits), it is first normalized to the ASCII version. The return
917  * value is nextChar itself, or its normalized version, if nextChar is a digit
918  * in non-ASCII format. This method assumes its input is either a digit or the
919  * plus sign.
920  *
921  * @param {string} nextChar
922  * @param {boolean} rememberPosition
923  * @return {string}
924  * @private
925  */
926 i18n.phonenumbers.AsYouTypeFormatter.prototype.
927     normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
928                                                     rememberPosition) {
929
930   /** @type {string} */
931   var normalizedChar;
932   if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
933     normalizedChar = nextChar;
934     this.accruedInputWithoutFormatting_.append(nextChar);
935   } else {
936     normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
937     this.accruedInputWithoutFormatting_.append(normalizedChar);
938     this.nationalNumber_.append(normalizedChar);
939   }
940   if (rememberPosition) {
941     this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
942   }
943   return normalizedChar;
944 };
945
946
947 /**
948  * @param {string} nextChar
949  * @return {string}
950  * @private
951  */
952 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
953     function(nextChar) {
954
955   /** @type {string} */
956   var formattingTemplate = this.formattingTemplate_.toString();
957   if (formattingTemplate.substring(this.lastMatchPosition_)
958       .search(this.digitPattern_) >= 0) {
959     /** @type {number} */
960     var digitPatternStart = formattingTemplate.search(this.digitPattern_);
961     /** @type {string} */
962     var tempTemplate = formattingTemplate.replace(this.digitPattern_, nextChar);
963     this.formattingTemplate_.clear();
964     this.formattingTemplate_.append(tempTemplate);
965     this.lastMatchPosition_ = digitPatternStart;
966     return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
967   } else {
968     if (this.possibleFormats_.length == 1) {
969       // More digits are entered than we could handle, and there are no other
970       // valid patterns to try.
971       this.ableToFormat_ = false;
972     }  // else, we just reset the formatting pattern.
973     this.currentFormattingPattern_ = '';
974     return this.accruedInput_.toString();
975   }
976 };