Add comment
[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.DIGIT_PLACEHOLDER_ = '\u2008';
63   /**
64    * @type {RegExp}
65    * @private
66    */
67   this.DIGIT_PATTERN_ = new RegExp(this.DIGIT_PLACEHOLDER_);
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    * This is set to true when we know the user is entering a full national
111    * significant number, since we have either detected a national prefix or an
112    * international dialing prefix. When this is true, we will no longer use
113    * local number formatting patterns.
114    * @type {boolean}
115    * @private
116    */
117   this.isCompleteNumber_ = false;
118   /**
119    * @type {boolean}
120    * @private
121    */
122   this.isExpectingCountryCallingCode_ = false;
123   /**
124    * @type {i18n.phonenumbers.PhoneNumberUtil}
125    * @private
126    */
127   this.phoneUtil_ = i18n.phonenumbers.PhoneNumberUtil.getInstance();
128   /**
129    * @type {number}
130    * @private
131    */
132   this.lastMatchPosition_ = 0;
133   /**
134    * The position of a digit upon which inputDigitAndRememberPosition is most
135    * recently invoked, as found in the original sequence of characters the user
136    * entered.
137    * @type {number}
138    * @private
139    */
140   this.originalPosition_ = 0;
141   /**
142    * The position of a digit upon which inputDigitAndRememberPosition is most
143    * recently invoked, as found in accruedInputWithoutFormatting.
144    * entered.
145    * @type {number}
146    * @private
147    */
148   this.positionToRemember_ = 0;
149   /**
150    * This contains anything that has been entered so far preceding the national
151    * significant number, and it is formatted (e.g. with space inserted). For
152    * example, this can contain IDD, country code, and/or NDD, etc.
153    * @type {!goog.string.StringBuffer}
154    * @private
155    */
156   this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
157   /**
158    * @type {boolean}
159    * @private
160    */
161   this.shouldAddSpaceAfterNationalPrefix_ = false;
162   /**
163    * This contains the national prefix that has been extracted. It contains only
164    * digits without formatting.
165    * @type {string}
166    * @private
167    */
168   this.extractedNationalPrefix_ = '';
169   /**
170    * @type {!goog.string.StringBuffer}
171    * @private
172    */
173   this.nationalNumber_ = new goog.string.StringBuffer();
174   /**
175    * @type {Array.<i18n.phonenumbers.NumberFormat>}
176    * @private
177    */
178   this.possibleFormats_ = [];
179   /**
180    * @type {string}
181    * @private
182    */
183   this.defaultCountry_ = regionCode;
184   this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
185   /**
186    * @type {i18n.phonenumbers.PhoneMetadata}
187    * @private
188    */
189   this.defaultMetadata_ = this.currentMetadata_;
190 };
191
192
193 /**
194  * Character used when appropriate to separate a prefix, such as a long NDD or a
195  * country calling code, from the national number.
196  * @const
197  * @type {string}
198  * @private
199  */
200 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ = ' ';
201
202
203 /**
204  * @const
205  * @type {i18n.phonenumbers.PhoneMetadata}
206  * @private
207  */
208 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
209     new i18n.phonenumbers.PhoneMetadata();
210 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
211     .setInternationalPrefix('NA');
212
213
214 /**
215  * A pattern that is used to match character classes in regular expressions.
216  * An example of a character class is [1-4].
217  * @const
218  * @type {RegExp}
219  * @private
220  */
221 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
222     /\[([^\[\]])*\]/g;
223
224
225 /**
226  * Any digit in a regular expression that actually denotes a digit. For
227  * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
228  * (8 and 0) are standalone digits, but the rest are not.
229  * Two look-aheads are needed because the number following \\d could be a
230  * two-digit number, since the phone number can be as long as 15 digits.
231  * @const
232  * @type {RegExp}
233  * @private
234  */
235 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
236     /\d(?=[^,}][^,}])/g;
237
238
239 /**
240  * A pattern that is used to determine if a numberFormat under availableFormats
241  * is eligible to be used by the AYTF. It is eligible when the format element
242  * under numberFormat contains groups of the dollar sign followed by a single
243  * digit, separated by valid phone number punctuation. This prevents invalid
244  * punctuation (such as the star sign in Israeli star numbers) getting into the
245  * output of the AYTF.
246  * @const
247  * @type {RegExp}
248  * @private
249  */
250 i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
251     '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
252     '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
253
254
255 /**
256  * A set of characters that, if found in a national prefix formatting rules, are
257  * an indicator to us that we should separate the national prefix from the
258  * number when formatting.
259  * @const
260  * @type {RegExp}
261  * @private
262  */
263 i18n.phonenumbers.AsYouTypeFormatter.NATIONAL_PREFIX_SEPARATORS_PATTERN_ =
264     /[- ]/;
265
266
267 /**
268  * This is the minimum length of national number accrued that is required to
269  * trigger the formatter. The first element of the leadingDigitsPattern of
270  * each numberFormat contains a regular expression that matches up to this
271  * number of digits.
272  * @const
273  * @type {number}
274  * @private
275  */
276 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
277
278
279 /**
280  * The metadata needed by this class is the same for all regions sharing the
281  * same country calling code. Therefore, we return the metadata for "main"
282  * region for this country calling code.
283  * @param {string} regionCode an ISO 3166-1 two-letter region code.
284  * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
285  * @private
286  */
287 i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
288     function(regionCode) {
289
290   /** @type {number} */
291   var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
292   /** @type {string} */
293   var mainCountry =
294       this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
295   /** @type {i18n.phonenumbers.PhoneMetadata} */
296   var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
297   if (metadata != null) {
298     return metadata;
299   }
300   // Set to a default instance of the metadata. This allows us to function with
301   // an incorrect region code, even if formatting only works for numbers
302   // specified with '+'.
303   return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
304 };
305
306
307 /**
308  * @return {boolean} true if a new template is created as opposed to reusing the
309  *     existing template.
310  * @private
311  */
312 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
313     function() {
314
315   // When there are multiple available formats, the formatter uses the first
316   // format where a formatting template could be created.
317   /** @type {number} */
318   var possibleFormatsLength = this.possibleFormats_.length;
319   for (var i = 0; i < possibleFormatsLength; ++i) {
320     /** @type {i18n.phonenumbers.NumberFormat} */
321     var numberFormat = this.possibleFormats_[i];
322     /** @type {string} */
323     var pattern = numberFormat.getPatternOrDefault();
324     if (this.currentFormattingPattern_ == pattern) {
325       return false;
326     }
327     if (this.createFormattingTemplate_(numberFormat)) {
328       this.currentFormattingPattern_ = pattern;
329       this.shouldAddSpaceAfterNationalPrefix_ =
330           i18n.phonenumbers.AsYouTypeFormatter.
331           NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
332               numberFormat.getNationalPrefixFormattingRule());
333       // With a new formatting template, the matched position using the old
334       // template needs to be reset.
335       this.lastMatchPosition_ = 0;
336       return true;
337     }
338   }
339   this.ableToFormat_ = false;
340   return false;
341 };
342
343
344 /**
345  * @param {string} leadingDigits leading digits of entered number.
346  * @private
347  */
348 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
349     function(leadingDigits) {
350
351   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
352   var formatList =
353       (this.isCompleteNumber_ &&
354            this.currentMetadata_.intlNumberFormatCount() > 0) ?
355       this.currentMetadata_.intlNumberFormatArray() :
356       this.currentMetadata_.numberFormatArray();
357   /** @type {number} */
358   var formatListLength = formatList.length;
359   for (var i = 0; i < formatListLength; ++i) {
360     /** @type {i18n.phonenumbers.NumberFormat} */
361     var format = formatList[i];
362     /** @type {boolean} */
363     var nationalPrefixIsUsedByCountry =
364         this.currentMetadata_.hasNationalPrefix();
365     if (!nationalPrefixIsUsedByCountry || this.isCompleteNumber_ ||
366         format.getNationalPrefixOptionalWhenFormatting() ||
367         this.phoneUtil_.formattingRuleHasFirstGroupOnly(
368             format.getNationalPrefixFormattingRuleOrDefault())) {
369       if (this.isFormatEligible_(format.getFormatOrDefault())) {
370         this.possibleFormats_.push(format);
371       }
372     }
373   }
374   this.narrowDownPossibleFormats_(leadingDigits);
375 };
376
377
378 /**
379  * @param {string} format
380  * @return {boolean}
381  * @private
382  */
383 i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
384     function(format) {
385   return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
386       .test(format);
387 };
388
389
390 /**
391  * @param {string} leadingDigits
392  * @private
393  */
394 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
395     function(leadingDigits) {
396
397   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
398   var possibleFormats = [];
399   /** @type {number} */
400   var indexOfLeadingDigitsPattern =
401       leadingDigits.length -
402       i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
403   /** @type {number} */
404   var possibleFormatsLength = this.possibleFormats_.length;
405   for (var i = 0; i < possibleFormatsLength; ++i) {
406     /** @type {i18n.phonenumbers.NumberFormat} */
407     var format = this.possibleFormats_[i];
408     if (format.leadingDigitsPatternCount() == 0) {
409       // Keep everything that isn't restricted by leading digits.
410       possibleFormats.push(this.possibleFormats_[i]);
411       continue;
412     }
413     /** @type {number} */
414     var lastLeadingDigitsPattern = Math.min(
415         indexOfLeadingDigitsPattern, format.leadingDigitsPatternCount() - 1);
416     /** @type {string} */
417     var leadingDigitsPattern = /** @type {string} */
418         (format.getLeadingDigitsPattern(lastLeadingDigitsPattern));
419     if (leadingDigits.search(leadingDigitsPattern) == 0) {
420       possibleFormats.push(this.possibleFormats_[i]);
421     }
422   }
423   this.possibleFormats_ = possibleFormats;
424 };
425
426
427 /**
428  * @param {i18n.phonenumbers.NumberFormat} format
429  * @return {boolean}
430  * @private
431  */
432 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
433     function(format) {
434
435   /** @type {string} */
436   var numberPattern = format.getPatternOrDefault();
437
438   // The formatter doesn't format numbers when numberPattern contains '|', e.g.
439   // (20|3)\d{4}. In those cases we quickly return.
440   if (numberPattern.indexOf('|') != -1) {
441     return false;
442   }
443
444   // Replace anything in the form of [..] with \d
445   numberPattern = numberPattern.replace(
446       i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
447
448   // Replace any standalone digit (not the one in d{}) with \d
449   numberPattern = numberPattern.replace(
450       i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
451   this.formattingTemplate_.clear();
452   /** @type {string} */
453   var tempTemplate = this.getFormattingTemplate_(numberPattern,
454                                                  format.getFormatOrDefault());
455   if (tempTemplate.length > 0) {
456     this.formattingTemplate_.append(tempTemplate);
457     return true;
458   }
459   return false;
460 };
461
462
463 /**
464  * Gets a formatting template which can be used to efficiently format a
465  * partial number where digits are added one by one.
466  *
467  * @param {string} numberPattern
468  * @param {string} numberFormat
469  * @return {string}
470  * @private
471  */
472 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
473     function(numberPattern, numberFormat) {
474
475   // Creates a phone number consisting only of the digit 9 that matches the
476   // numberPattern by applying the pattern to the longestPhoneNumber string.
477   /** @type {string} */
478   var longestPhoneNumber = '999999999999999';
479   /** @type {Array.<string>} */
480   var m = longestPhoneNumber.match(numberPattern);
481   // this match will always succeed
482   /** @type {string} */
483   var aPhoneNumber = m[0];
484   // No formatting template can be created if the number of digits entered so
485   // far is longer than the maximum the current formatting rule can accommodate.
486   if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
487     return '';
488   }
489   // Formats the number according to numberFormat
490   /** @type {string} */
491   var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
492                                       numberFormat);
493   // Replaces each digit with character DIGIT_PLACEHOLDER
494   template = template.replace(new RegExp('9', 'g'), this.DIGIT_PLACEHOLDER_);
495   return template;
496 };
497
498
499 /**
500  * Clears the internal state of the formatter, so it can be reused.
501  */
502 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
503   this.currentOutput_ = '';
504   this.accruedInput_.clear();
505   this.accruedInputWithoutFormatting_.clear();
506   this.formattingTemplate_.clear();
507   this.lastMatchPosition_ = 0;
508   this.currentFormattingPattern_ = '';
509   this.prefixBeforeNationalNumber_.clear();
510   this.extractedNationalPrefix_ = '';
511   this.nationalNumber_.clear();
512   this.ableToFormat_ = true;
513   this.inputHasFormatting_ = false;
514   this.positionToRemember_ = 0;
515   this.originalPosition_ = 0;
516   this.isCompleteNumber_ = false;
517   this.isExpectingCountryCallingCode_ = false;
518   this.possibleFormats_ = [];
519   this.shouldAddSpaceAfterNationalPrefix_ = false;
520   if (this.currentMetadata_ != this.defaultMetadata_) {
521     this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
522   }
523 };
524
525
526 /**
527  * Formats a phone number on-the-fly as each digit is entered.
528  *
529  * @param {string} nextChar the most recently entered digit of a phone number.
530  *     Formatting characters are allowed, but as soon as they are encountered
531  *     this method formats the number as entered and not 'as you type' anymore.
532  *     Full width digits and Arabic-indic digits are allowed, and will be shown
533  *     as they are.
534  * @return {string} the partially formatted phone number.
535  */
536 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
537   this.currentOutput_ =
538       this.inputDigitWithOptionToRememberPosition_(nextChar, false);
539   return this.currentOutput_;
540 };
541
542
543 /**
544  * Same as {@link #inputDigit}, but remembers the position where
545  * {@code nextChar} is inserted, so that it can be retrieved later by using
546  * {@link #getRememberedPosition}. The remembered position will be automatically
547  * adjusted if additional formatting characters are later inserted/removed in
548  * front of {@code nextChar}.
549  *
550  * @param {string} nextChar
551  * @return {string}
552  */
553 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
554     function(nextChar) {
555
556   this.currentOutput_ =
557       this.inputDigitWithOptionToRememberPosition_(nextChar, true);
558   return this.currentOutput_;
559 };
560
561
562 /**
563  * @param {string} nextChar
564  * @param {boolean} rememberPosition
565  * @return {string}
566  * @private
567  */
568 i18n.phonenumbers.AsYouTypeFormatter.prototype.
569     inputDigitWithOptionToRememberPosition_ = function(nextChar,
570                                                        rememberPosition) {
571
572   this.accruedInput_.append(nextChar);
573   if (rememberPosition) {
574     this.originalPosition_ = this.accruedInput_.getLength();
575   }
576   // We do formatting on-the-fly only when each character entered is either a
577   // digit, or a plus sign (accepted at the start of the number only).
578   if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
579     this.ableToFormat_ = false;
580     this.inputHasFormatting_ = true;
581   } else {
582     nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
583                                                          rememberPosition);
584   }
585   if (!this.ableToFormat_) {
586     // When we are unable to format because of reasons other than that
587     // formatting chars have been entered, it can be due to really long IDDs or
588     // NDDs. If that is the case, we might be able to do formatting again after
589     // extracting them.
590     if (this.inputHasFormatting_) {
591       return this.accruedInput_.toString();
592     } else if (this.attemptToExtractIdd_()) {
593       if (this.attemptToExtractCountryCallingCode_()) {
594         return this.attemptToChoosePatternWithPrefixExtracted_();
595       }
596     } else if (this.ableToExtractLongerNdd_()) {
597       // Add an additional space to separate long NDD and national significant
598       // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
599       // to true, since we don't want this to change later when we choose
600       // formatting templates.
601       this.prefixBeforeNationalNumber_.append(
602           i18n.phonenumbers.AsYouTypeFormatter.
603           SEPARATOR_BEFORE_NATIONAL_NUMBER_);
604       return this.attemptToChoosePatternWithPrefixExtracted_();
605     }
606     return this.accruedInput_.toString();
607   }
608
609   // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
610   // digits (the plus sign is counted as a digit as well for this purpose) have
611   // been entered.
612   switch (this.accruedInputWithoutFormatting_.getLength()) {
613     case 0:
614     case 1:
615     case 2:
616       return this.accruedInput_.toString();
617     case 3:
618       if (this.attemptToExtractIdd_()) {
619         this.isExpectingCountryCallingCode_ = true;
620       } else {
621         // No IDD or plus sign is found, might be entering in national format.
622         this.extractedNationalPrefix_ =
623             this.removeNationalPrefixFromNationalNumber_();
624         return this.attemptToChooseFormattingPattern_();
625       }
626     default:
627       if (this.isExpectingCountryCallingCode_) {
628         if (this.attemptToExtractCountryCallingCode_()) {
629           this.isExpectingCountryCallingCode_ = false;
630         }
631         return this.prefixBeforeNationalNumber_.toString() +
632             this.nationalNumber_.toString();
633       }
634       if (this.possibleFormats_.length > 0) {
635         // The formatting patterns are already chosen.
636         /** @type {string} */
637         var tempNationalNumber = this.inputDigitHelper_(nextChar);
638         // See if the accrued digits can be formatted properly already. If not,
639         // use the results from inputDigitHelper, which does formatting based on
640         // the formatting pattern chosen.
641         /** @type {string} */
642         var formattedNumber = this.attemptToFormatAccruedDigits_();
643         if (formattedNumber.length > 0) {
644           return formattedNumber;
645         }
646         this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
647         if (this.maybeCreateNewTemplate_()) {
648           return this.inputAccruedNationalNumber_();
649         }
650         return this.ableToFormat_ ?
651             this.appendNationalNumber_(tempNationalNumber) :
652             this.accruedInput_.toString();
653       } else {
654         return this.attemptToChooseFormattingPattern_();
655       }
656   }
657 };
658
659
660 /**
661  * @return {string}
662  * @private
663  */
664 i18n.phonenumbers.AsYouTypeFormatter.prototype.
665     attemptToChoosePatternWithPrefixExtracted_ = function() {
666
667   this.ableToFormat_ = true;
668   this.isExpectingCountryCallingCode_ = false;
669   this.possibleFormats_ = [];
670   this.lastMatchPosition_ = 0;
671   this.formattingTemplate_.clear();
672   this.currentFormattingPattern_ = '';
673   return this.attemptToChooseFormattingPattern_();
674 };
675
676
677 /**
678  * @return {string}
679  * @private
680  */
681 i18n.phonenumbers.AsYouTypeFormatter.prototype.getExtractedNationalPrefix_ =
682     function() {
683   return this.extractedNationalPrefix_;
684 };
685
686
687 /**
688  * Some national prefixes are a substring of others. If extracting the shorter
689  * NDD doesn't result in a number we can format, we try to see if we can extract
690  * a longer version here.
691  * @return {boolean}
692  * @private
693  */
694 i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
695     function() {
696   if (this.extractedNationalPrefix_.length > 0) {
697     // Put the extracted NDD back to the national number before attempting to
698     // extract a new NDD.
699     /** @type {string} */
700     var nationalNumberStr = this.nationalNumber_.toString();
701     this.nationalNumber_.clear();
702     this.nationalNumber_.append(this.extractedNationalPrefix_);
703     this.nationalNumber_.append(nationalNumberStr);
704     // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
705     // cannot simply set it to empty string because people sometimes incorrectly
706     // enter national prefix after the country code, e.g. +44 (0)20-1234-5678.
707     /** @type {string} */
708     var prefixBeforeNationalNumberStr =
709         this.prefixBeforeNationalNumber_.toString();
710     /** @type {number} */
711     var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
712         this.extractedNationalPrefix_);
713     this.prefixBeforeNationalNumber_.clear();
714     this.prefixBeforeNationalNumber_.append(
715         prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
716   }
717   return this.extractedNationalPrefix_ !=
718       this.removeNationalPrefixFromNationalNumber_();
719 };
720
721
722 /**
723  * @param {string} nextChar
724  * @return {boolean}
725  * @private
726  */
727 i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
728     function(nextChar) {
729   return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
730       .test(nextChar) ||
731       (this.accruedInput_.getLength() == 1 &&
732        i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
733 };
734
735
736 /**
737  * Check to see if there is an exact pattern match for these digits. If so, we
738  * should use this instead of any other formatting template whose
739  * leadingDigitsPattern also matches the input.
740  * @return {string}
741  * @private
742  */
743 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
744     function() {
745
746   /** @type {string} */
747   var nationalNumber = this.nationalNumber_.toString();
748   /** @type {number} */
749   var possibleFormatsLength = this.possibleFormats_.length;
750   for (var i = 0; i < possibleFormatsLength; ++i) {
751     /** @type {i18n.phonenumbers.NumberFormat} */
752     var numberFormat = this.possibleFormats_[i];
753     /** @type {string} */
754     var pattern = numberFormat.getPatternOrDefault();
755     /** @type {RegExp} */
756     var patternRegExp = new RegExp('^(?:' + pattern + ')$');
757     if (patternRegExp.test(nationalNumber)) {
758       this.shouldAddSpaceAfterNationalPrefix_ =
759           i18n.phonenumbers.AsYouTypeFormatter.
760           NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
761               numberFormat.getNationalPrefixFormattingRule());
762       /** @type {string} */
763       var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
764                                                    numberFormat.getFormat());
765       return this.appendNationalNumber_(formattedNumber);
766     }
767   }
768   return '';
769 };
770
771
772 /**
773  * Combines the national number with any prefix (IDD/+ and country code or
774  * national prefix) that was collected. A space will be inserted between them if
775  * the current formatting template indicates this to be suitable.
776  * @param {string} nationalNumber The number to be appended.
777  * @return {string} The combined number.
778  * @private
779  */
780 i18n.phonenumbers.AsYouTypeFormatter.prototype.appendNationalNumber_ =
781     function(nationalNumber) {
782   /** @type {number} */
783   var prefixBeforeNationalNumberLength =
784       this.prefixBeforeNationalNumber_.getLength();
785   if (this.shouldAddSpaceAfterNationalPrefix_ &&
786       prefixBeforeNationalNumberLength > 0 &&
787       this.prefixBeforeNationalNumber_.toString().charAt(
788           prefixBeforeNationalNumberLength - 1) !=
789       i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_) {
790     // We want to add a space after the national prefix if the national prefix
791     // formatting rule indicates that this would normally be done, with the
792     // exception of the case where we already appended a space because the NDD
793     // was surprisingly long.
794     return this.prefixBeforeNationalNumber_ +
795         i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ +
796         nationalNumber;
797   } else {
798     return this.prefixBeforeNationalNumber_ + nationalNumber;
799   }
800 };
801
802
803 /**
804  * Returns the current position in the partially formatted phone number of the
805  * character which was previously passed in as the parameter of
806  * {@link #inputDigitAndRememberPosition}.
807  *
808  * @return {number}
809  */
810 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
811     function() {
812
813   if (!this.ableToFormat_) {
814     return this.originalPosition_;
815   }
816   /** @type {number} */
817   var accruedInputIndex = 0;
818   /** @type {number} */
819   var currentOutputIndex = 0;
820   /** @type {string} */
821   var accruedInputWithoutFormatting =
822       this.accruedInputWithoutFormatting_.toString();
823   /** @type {string} */
824   var currentOutput = this.currentOutput_.toString();
825   while (accruedInputIndex < this.positionToRemember_ &&
826          currentOutputIndex < currentOutput.length) {
827     if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
828         currentOutput.charAt(currentOutputIndex)) {
829       accruedInputIndex++;
830     }
831     currentOutputIndex++;
832   }
833   return currentOutputIndex;
834 };
835
836
837 /**
838  * Attempts to set the formatting template and returns a string which contains
839  * the formatted version of the digits entered so far.
840  *
841  * @return {string}
842  * @private
843  */
844 i18n.phonenumbers.AsYouTypeFormatter.prototype.
845     attemptToChooseFormattingPattern_ = function() {
846
847   /** @type {string} */
848   var nationalNumber = this.nationalNumber_.toString();
849   // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
850   // digits of national number (excluding national prefix) have been entered.
851   if (nationalNumber.length >=
852       i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
853     this.getAvailableFormats_(nationalNumber);
854     // See if the accrued digits can be formatted properly already.
855     var formattedNumber = this.attemptToFormatAccruedDigits_();
856     if (formattedNumber.length > 0) {
857       return formattedNumber;
858     }
859     return this.maybeCreateNewTemplate_() ?
860         this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
861   } else {
862     return this.appendNationalNumber_(nationalNumber);
863   }
864 };
865
866
867 /**
868  * Invokes inputDigitHelper on each digit of the national number accrued, and
869  * returns a formatted string in the end.
870  *
871  * @return {string}
872  * @private
873  */
874 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
875     function() {
876
877   /** @type {string} */
878   var nationalNumber = this.nationalNumber_.toString();
879   /** @type {number} */
880   var lengthOfNationalNumber = nationalNumber.length;
881   if (lengthOfNationalNumber > 0) {
882     /** @type {string} */
883     var tempNationalNumber = '';
884     for (var i = 0; i < lengthOfNationalNumber; i++) {
885       tempNationalNumber =
886           this.inputDigitHelper_(nationalNumber.charAt(i));
887     }
888     return this.ableToFormat_ ?
889         this.appendNationalNumber_(tempNationalNumber) :
890         this.accruedInput_.toString();
891   } else {
892     return this.prefixBeforeNationalNumber_.toString();
893   }
894 };
895
896
897 /**
898  * @return {boolean} true if the current country is a NANPA country and the
899  *     national number begins with the national prefix.
900  * @private
901  */
902 i18n.phonenumbers.AsYouTypeFormatter.prototype.
903     isNanpaNumberWithNationalPrefix_ = function() {
904   // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
905   // prefix. The reason is that national significant numbers in NANPA always
906   // start with [2-9] after the national prefix. Numbers beginning with 1[01]
907   // can only be short/emergency numbers, which don't need the national prefix.
908   if (this.currentMetadata_.getCountryCode() != 1) {
909     return false;
910   }
911   /** @type {string} */
912   var nationalNumber = this.nationalNumber_.toString();
913   return (nationalNumber.charAt(0) == '1') &&
914       (nationalNumber.charAt(1) != '0') &&
915       (nationalNumber.charAt(1) != '1');
916 };
917
918
919 /**
920  * Returns the national prefix extracted, or an empty string if it is not
921  * present.
922  * @return {string}
923  * @private
924  */
925 i18n.phonenumbers.AsYouTypeFormatter.prototype.
926     removeNationalPrefixFromNationalNumber_ = function() {
927
928   /** @type {string} */
929   var nationalNumber = this.nationalNumber_.toString();
930   /** @type {number} */
931   var startOfNationalNumber = 0;
932   if (this.isNanpaNumberWithNationalPrefix_()) {
933     startOfNationalNumber = 1;
934     this.prefixBeforeNationalNumber_.append('1').append(
935         i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
936     this.isCompleteNumber_ = true;
937   } else if (this.currentMetadata_.hasNationalPrefixForParsing()) {
938     /** @type {RegExp} */
939     var nationalPrefixForParsing = new RegExp(
940         '^(?:' + this.currentMetadata_.getNationalPrefixForParsing() + ')');
941     /** @type {Array.<string>} */
942     var m = nationalNumber.match(nationalPrefixForParsing);
943     // Since some national prefix patterns are entirely optional, check that a
944     // national prefix could actually be extracted.
945     if (m != null && m[0] != null && m[0].length > 0) {
946       // When the national prefix is detected, we use international formatting
947       // rules instead of national ones, because national formatting rules could
948       // contain local formatting rules for numbers entered without area code.
949       this.isCompleteNumber_ = true;
950       startOfNationalNumber = m[0].length;
951       this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
952           startOfNationalNumber));
953     }
954   }
955   this.nationalNumber_.clear();
956   this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
957   return nationalNumber.substring(0, startOfNationalNumber);
958 };
959
960
961 /**
962  * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
963  * available, and places the remaining input into nationalNumber.
964  *
965  * @return {boolean} true when accruedInputWithoutFormatting begins with the
966  *     plus sign or valid IDD for defaultCountry.
967  * @private
968  */
969 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
970     function() {
971
972   /** @type {string} */
973   var accruedInputWithoutFormatting =
974       this.accruedInputWithoutFormatting_.toString();
975   /** @type {RegExp} */
976   var internationalPrefix = new RegExp(
977       '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
978       this.currentMetadata_.getInternationalPrefix() + ')');
979   /** @type {Array.<string>} */
980   var m = accruedInputWithoutFormatting.match(internationalPrefix);
981   if (m != null && m[0] != null && m[0].length > 0) {
982     this.isCompleteNumber_ = true;
983     /** @type {number} */
984     var startOfCountryCallingCode = m[0].length;
985     this.nationalNumber_.clear();
986     this.nationalNumber_.append(
987         accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
988     this.prefixBeforeNationalNumber_.clear();
989     this.prefixBeforeNationalNumber_.append(
990         accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
991     if (accruedInputWithoutFormatting.charAt(0) !=
992         i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
993       this.prefixBeforeNationalNumber_.append(
994           i18n.phonenumbers.AsYouTypeFormatter.
995           SEPARATOR_BEFORE_NATIONAL_NUMBER_);
996     }
997     return true;
998   }
999   return false;
1000 };
1001
1002
1003 /**
1004  * Extracts the country calling code from the beginning of nationalNumber to
1005  * prefixBeforeNationalNumber when they are available, and places the remaining
1006  * input into nationalNumber.
1007  *
1008  * @return {boolean} true when a valid country calling code can be found.
1009  * @private
1010  */
1011 i18n.phonenumbers.AsYouTypeFormatter.prototype.
1012     attemptToExtractCountryCallingCode_ = function() {
1013
1014   if (this.nationalNumber_.getLength() == 0) {
1015     return false;
1016   }
1017   /** @type {!goog.string.StringBuffer} */
1018   var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
1019   /** @type {number} */
1020   var countryCode = this.phoneUtil_.extractCountryCode(
1021       this.nationalNumber_, numberWithoutCountryCallingCode);
1022   if (countryCode == 0) {
1023     return false;
1024   }
1025   this.nationalNumber_.clear();
1026   this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
1027   /** @type {string} */
1028   var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
1029   if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
1030       newRegionCode) {
1031     this.currentMetadata_ =
1032         this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
1033   } else if (newRegionCode != this.defaultCountry_) {
1034     this.currentMetadata_ = this.getMetadataForRegion_(newRegionCode);
1035   }
1036   /** @type {string} */
1037   var countryCodeString = '' + countryCode;
1038   this.prefixBeforeNationalNumber_.append(countryCodeString).append(
1039       i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
1040   // When we have successfully extracted the IDD, the previously extracted NDD
1041   // should be cleared because it is no longer valid.
1042   this.extractedNationalPrefix_ = '';
1043   return true;
1044 };
1045
1046
1047 /**
1048  * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
1049  * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
1050  * version of digits), it is first normalized to the ASCII version. The return
1051  * value is nextChar itself, or its normalized version, if nextChar is a digit
1052  * in non-ASCII format. This method assumes its input is either a digit or the
1053  * plus sign.
1054  *
1055  * @param {string} nextChar
1056  * @param {boolean} rememberPosition
1057  * @return {string}
1058  * @private
1059  */
1060 i18n.phonenumbers.AsYouTypeFormatter.prototype.
1061     normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
1062                                                     rememberPosition) {
1063
1064   /** @type {string} */
1065   var normalizedChar;
1066   if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
1067     normalizedChar = nextChar;
1068     this.accruedInputWithoutFormatting_.append(nextChar);
1069   } else {
1070     normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
1071     this.accruedInputWithoutFormatting_.append(normalizedChar);
1072     this.nationalNumber_.append(normalizedChar);
1073   }
1074   if (rememberPosition) {
1075     this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
1076   }
1077   return normalizedChar;
1078 };
1079
1080
1081 /**
1082  * @param {string} nextChar
1083  * @return {string}
1084  * @private
1085  */
1086 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
1087     function(nextChar) {
1088
1089   // Note that formattingTemplate is not guaranteed to have a value, it could be
1090   // empty, e.g. when the next digit is entered after extracting an IDD or NDD.
1091   /** @type {string} */
1092   var formattingTemplate = this.formattingTemplate_.toString();
1093   if (formattingTemplate.substring(this.lastMatchPosition_)
1094       .search(this.DIGIT_PATTERN_) >= 0) {
1095     /** @type {number} */
1096     var digitPatternStart = formattingTemplate.search(this.DIGIT_PATTERN_);
1097     /** @type {string} */
1098     var tempTemplate =
1099         formattingTemplate.replace(this.DIGIT_PATTERN_, nextChar);
1100     this.formattingTemplate_.clear();
1101     this.formattingTemplate_.append(tempTemplate);
1102     this.lastMatchPosition_ = digitPatternStart;
1103     return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
1104   } else {
1105     if (this.possibleFormats_.length == 1) {
1106       // More digits are entered than we could handle, and there are no other
1107       // valid patterns to try.
1108       this.ableToFormat_ = false;
1109     }  // else, we just reset the formatting pattern.
1110     this.currentFormattingPattern_ = '';
1111     return this.accruedInput_.toString();
1112   }
1113 };