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