JS: Update JS version to v3.0. findNumbers is not included yet. Patch contributed...
[platform/upstream/libphonenumber.git] / javascript / i18n / phonenumbers / asyoutypeformatter.js
1 /*
2  * @license
3  * Copyright (C) 2010 Google Inc.
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  * An AsYouTypeFormatter could be created by new AsYouTypeFormatter(). After
23  * that digits could be added by invoking the inputDigit method on the formatter
24  * instance, and the partially formatted phone number will be returned each time
25  * a digit is added. The clear method should be invoked before a new number
26  * needs to be formatted.
27  *
28  * See testAsYouTypeFormatterUS(), testAsYouTestFormatterGB() and
29  * testAsYouTypeFormatterDE() in asyoutypeformatter_test.js for more details
30  * on how the formatter is to be used.
31  *
32  * @author Nikolaos Trogkanis
33  */
34
35 goog.provide('i18n.phonenumbers.AsYouTypeFormatter');
36
37 goog.require('goog.string.StringBuffer');
38 goog.require('i18n.phonenumbers.NumberFormat');
39 goog.require('i18n.phonenumbers.PhoneMetadata');
40 goog.require('i18n.phonenumbers.PhoneMetadataCollection');
41 goog.require('i18n.phonenumbers.PhoneNumber');
42 goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource');
43 goog.require('i18n.phonenumbers.PhoneNumberDesc');
44 goog.require('i18n.phonenumbers.PhoneNumberUtil');
45 goog.require('i18n.phonenumbers.metadata');
46
47
48
49 /**
50  * Constructs a light-weight formatter which does no formatting, but outputs
51  * exactly what is fed into the inputDigit method.
52  *
53  * @param {string} regionCode the country/region where the phone number is being
54  *     entered.
55  * @constructor
56  */
57 i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
58   /**
59    * A pattern that is used to match character classes in regular expressions.
60    * An example of a character class is [1-4].
61    * @const
62    * @type {RegExp}
63    * @private
64    */
65   this.CHARACTER_CLASS_PATTERN_ = /\[([^\[\]])*\]/g;
66   /**
67    * Any digit in a regular expression that actually denotes a digit. For
68    * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
69    * (8 and 0) are standalone digits, but the rest are not.
70    * Two look-aheads are needed because the number following \\d could be a
71    * two-digit number, since the phone number can be as long as 15 digits.
72    * @const
73    * @type {RegExp}
74    * @private
75    */
76   this.STANDALONE_DIGIT_PATTERN_ = /\d(?=[^,}][^,}])/g;
77   /**
78    * This is the minimum length of national number accrued that is required to
79    * trigger the formatter. The first element of the leadingDigitsPattern of
80    * each numberFormat contains a regular expression that matches up to this
81    * number of digits.
82    * @const
83    * @type {number}
84    * @private
85    */
86   this.MIN_LEADING_DIGITS_LENGTH_ = 3;
87   /**
88    * The digits that have not been entered yet will be represented by a \u2008,
89    * the punctuation space.
90    * @const
91    * @type {string}
92    * @private
93    */
94   this.digitPlaceholder_ = '\u2008';
95   /**
96    * @type {RegExp}
97    * @private
98    */
99   this.digitPattern_ = new RegExp(this.digitPlaceholder_);
100   /**
101    * @type {string}
102    * @private
103    */
104   this.currentOutput_ = '';
105   /**
106    * @type {!goog.string.StringBuffer}
107    * @private
108    */
109   this.formattingTemplate_ = new goog.string.StringBuffer();
110   /**
111    * The pattern from numberFormat that is currently used to create
112    * formattingTemplate.
113    * @type {string}
114    * @private
115    */
116   this.currentFormattingPattern_ = '';
117   /**
118    * @type {!goog.string.StringBuffer}
119    * @private
120    */
121   this.accruedInput_ = new goog.string.StringBuffer();
122   /**
123    * @type {!goog.string.StringBuffer}
124    * @private
125    */
126   this.accruedInputWithoutFormatting_ = new goog.string.StringBuffer();
127   /**
128    * @type {boolean}
129    * @private
130    */
131   this.ableToFormat_ = true;
132   /**
133    * @type {boolean}
134    * @private
135    */
136   this.isInternationalFormatting_ = false;
137   /**
138    * @type {boolean}
139    * @private
140    */
141   this.isExpectingCountryCode_ = false;
142   /**
143    * @type {i18n.phonenumbers.PhoneNumberUtil}
144    * @private
145    */
146   this.phoneUtil_ = i18n.phonenumbers.PhoneNumberUtil.getInstance();
147   /**
148    * @type {number}
149    * @private
150    */
151   this.lastMatchPosition_ = 0;
152   /**
153    * The position of a digit upon which inputDigitAndRememberPosition is most
154    * recently invoked, as found in the original sequence of characters the user
155    * entered.
156    * @type {number}
157    * @private
158    */
159   this.originalPosition_ = 0;
160   /**
161    * The position of a digit upon which inputDigitAndRememberPosition is most
162    * recently invoked, as found in accruedInputWithoutFormatting.
163    * entered.
164    * @type {number}
165    * @private
166    */
167   this.positionToRemember_ = 0;
168   /**
169    * @type {!goog.string.StringBuffer}
170    * @private
171    */
172   this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
173   /**
174    * @type {!goog.string.StringBuffer}
175    * @private
176    */
177   this.nationalNumber_ = new goog.string.StringBuffer();
178   /**
179    * @type {Array.<i18n.phonenumbers.NumberFormat>}
180    * @private
181    */
182   this.possibleFormats_ = [];
183   /**
184    * @type {string}
185    * @private
186    */
187   this.defaultCountry_ = regionCode;
188   this.initializeCountrySpecificInfo_(this.defaultCountry_);
189   /**
190    * @type {i18n.phonenumbers.PhoneMetadata}
191    * @private
192    */
193   this.defaultMetaData_ = this.currentMetaData_;
194 };
195
196
197 /**
198  * @param {string} regionCode
199  * @private
200  */
201 i18n.phonenumbers.AsYouTypeFormatter.prototype.initializeCountrySpecificInfo_ =
202     function(regionCode) {
203
204   /** @type {i18n.phonenumbers.PhoneMetadata} */
205   this.currentMetaData_ = this.phoneUtil_.getMetadataForRegion(regionCode);
206   if (this.currentMetaData_ == null) {
207     // Set to a default instance of the metadata. This allows us to function
208     // with an incorrect region code, even if formatting only works for numbers
209     // specified with '+'.
210     this.currentMetaData_ = new i18n.phonenumbers.PhoneMetadata();
211     this.currentMetaData_.setInternationalPrefix('NA');
212   }
213   /** @type {RegExp} */
214   this.nationalPrefixForParsing_ = new RegExp('^(' + this.currentMetaData_
215       .getNationalPrefixForParsing() + ')');
216   /** @type {RegExp} */
217   this.internationalPrefix_ = new RegExp('^(' + '\\+|' +
218       this.currentMetaData_.getInternationalPrefix() + ')');
219 };
220
221
222 /**
223  * @return {boolean} true if a new template is created as opposed to reusing the
224  *     existing template.
225  * @private
226  */
227 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
228     function() {
229
230   // When there are multiple available formats, the formatter uses the first
231   // format where a formatting template could be created.
232   /** @type {number} */
233   var possibleFormatsLength = this.possibleFormats_.length;
234   for (var i = 0; i < possibleFormatsLength; ++i) {
235     /** @type {i18n.phonenumbers.NumberFormat} */
236     var numberFormat = this.possibleFormats_[i];
237     /** @type {string} */
238     var pattern = numberFormat.getPatternOrDefault();
239     if (this.currentFormattingPattern_ == pattern) {
240       return false;
241     }
242     if (this.createFormattingTemplate_(numberFormat)) {
243       this.currentFormattingPattern_ = pattern;
244       return true;
245     }
246   }
247   this.ableToFormat_ = false;
248   return false;
249 };
250
251
252 /**
253  * @param {string} leadingThreeDigits
254  * @private
255  */
256 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
257     function(leadingThreeDigits) {
258
259   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
260   var formatList = (this.isInternationalFormatting_ && this.currentMetaData_
261       .intlNumberFormatCount() > 0) ? this.currentMetaData_
262       .intlNumberFormatArray() : this.currentMetaData_.numberFormatArray();
263   this.possibleFormats_ = formatList;
264   this.narrowDownPossibleFormats_(leadingThreeDigits);
265 };
266
267
268 /**
269  * @param {string} leadingDigits
270  * @private
271  */
272 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
273     function(leadingDigits) {
274
275   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
276   var possibleFormats = [];
277   /** @type {number} */
278   var lengthOfLeadingDigits = leadingDigits.length;
279   /** @type {number} */
280   var indexOfLeadingDigitsPattern =
281       lengthOfLeadingDigits - this.MIN_LEADING_DIGITS_LENGTH_;
282   /** @type {number} */
283   var possibleFormatsLength = this.possibleFormats_.length;
284   for (var i = 0; i < possibleFormatsLength; ++i) {
285     /** @type {i18n.phonenumbers.NumberFormat} */
286     var format = this.possibleFormats_[i];
287     if (format.leadingDigitsPatternCount() > indexOfLeadingDigitsPattern) {
288       /** @type {RegExp} */
289       var leadingDigitsPattern = new RegExp('^(' +
290           format.getLeadingDigitsPattern(indexOfLeadingDigitsPattern) + ')');
291       if (leadingDigitsPattern.test(leadingDigits)) {
292         possibleFormats.push(this.possibleFormats_[i]);
293       }
294     } else {
295       // else the particular format has no more specific leadingDigitsPattern,
296       // and it should be retained.
297       possibleFormats.push(this.possibleFormats_[i]);
298     }
299   }
300   this.possibleFormats_ = possibleFormats;
301 };
302
303
304 /**
305  * @param {i18n.phonenumbers.NumberFormat} format
306  * @return {boolean}
307  * @private
308  */
309 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
310     function(format) {
311
312   /** @type {string} */
313   var numberFormat = format.getFormatOrDefault();
314   /** @type {string} */
315   var numberPattern = format.getPatternOrDefault();
316
317   // The formatter doesn't format numbers when numberPattern contains '|', e.g.
318   // (20|3)\d{4}. In those cases we quickly return.
319   if (numberPattern.indexOf('|') != -1) {
320     return false;
321   }
322
323   // Replace anything in the form of [..] with \d
324   numberPattern = numberPattern.replace(this.CHARACTER_CLASS_PATTERN_, '\\d');
325
326   // Replace any standalone digit (not the one in d{}) with \d
327   numberPattern = numberPattern.replace(this.STANDALONE_DIGIT_PATTERN_, '\\d');
328   this.formattingTemplate_.clear();
329   /** @type {string} */
330   var tempTemplate = this.getFormattingTemplate_(numberPattern, numberFormat);
331   if (tempTemplate.length > this.nationalNumber_.getLength()) {
332     this.formattingTemplate_.append(tempTemplate);
333     return true;
334   }
335   return false;
336 };
337
338
339 /**
340  * Gets a formatting template which could be used to efficiently format a
341  * partial number where digits are added one by one.
342  *
343  * @param {string} numberPattern
344  * @param {string} numberFormat
345  * @return {string}
346  * @private
347  */
348 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
349     function(numberPattern, numberFormat) {
350
351   // Creates a phone number consisting only of the digit 9 that matches the
352   // numberPattern by applying the pattern to the longestPhoneNumber string.
353   /** @type {string} */
354   var longestPhoneNumber = '999999999999999';
355   /** @type {Array.<string>} */
356   var m = longestPhoneNumber.match(numberPattern);
357   // this match will always succeed
358   /** @type {string} */
359   var aPhoneNumber = m[0];
360   // Formats the number according to numberFormat
361   /** @type {string} */
362   var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
363                                       numberFormat);
364   // Replaces each digit with character digitPlaceholder
365   template = template.replace(new RegExp('9', 'g'), this.digitPlaceholder_);
366   return template;
367 };
368
369
370 /**
371  * Clears the internal state of the formatter, so it could be reused.
372  */
373 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
374   this.currentOutput_ = '';
375   this.accruedInput_.clear();
376   this.accruedInputWithoutFormatting_.clear();
377   this.formattingTemplate_.clear();
378   this.lastMatchPosition_ = 0;
379   this.currentFormattingPattern_ = '';
380   this.prefixBeforeNationalNumber_.clear();
381   this.nationalNumber_.clear();
382   this.ableToFormat_ = true;
383   this.positionToRemember_ = 0;
384   this.originalPosition_ = 0;
385   this.isInternationalFormatting_ = false;
386   this.isExpectingCountryCode_ = false;
387   this.possibleFormats_ = [];
388   if (this.currentMetaData_ != this.defaultMetaData_) {
389     this.initializeCountrySpecificInfo_(this.defaultCountry_);
390   }
391 };
392
393
394 /**
395  * Formats a phone number on-the-fly as each digit is entered.
396  *
397  * @param {string} nextChar the most recently entered digit of a phone number.
398  *     Formatting characters are allowed, but they are removed from the result.
399  * @return {string} the partially formatted phone number.
400  */
401 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
402   this.currentOutput_ =
403       this.inputDigitWithOptionToRememberPosition_(nextChar, false);
404   return this.currentOutput_;
405 };
406
407
408 /**
409  * Same as inputDigit, but remembers the position where nextChar is inserted, so
410  * that it could be retrieved later by using getRememberedPosition(). The
411  * remembered position will be automatically adjusted if additional formatting
412  * characters are later inserted/removed in front of nextChar.
413  *
414  * @param {string} nextChar
415  * @return {string}
416  */
417 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
418     function(nextChar) {
419
420   this.currentOutput_ =
421       this.inputDigitWithOptionToRememberPosition_(nextChar, true);
422   return this.currentOutput_;
423 };
424
425
426 /**
427  * @param {string} nextChar
428  * @param {boolean} rememberPosition
429  * @return {string}
430  * @private
431  */
432 i18n.phonenumbers.AsYouTypeFormatter.prototype.
433     inputDigitWithOptionToRememberPosition_ = function(nextChar,
434                                                        rememberPosition) {
435
436   this.accruedInput_.append(nextChar);
437   if (rememberPosition) {
438     this.originalPosition_ = this.accruedInput_.getLength();
439   }
440   // We do formatting on-the-fly only when each character entered is either a
441   // plus sign or a digit.
442   if (!i18n.phonenumbers.PhoneNumberUtil.VALID_START_CHAR_PATTERN
443       .test(nextChar)) {
444     this.ableToFormat_ = false;
445   }
446   if (!this.ableToFormat_) {
447     return this.accruedInput_.toString();
448   }
449
450   nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
451                                                        rememberPosition);
452
453   // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
454   // digits (the plus sign is counted as a digit as well for this purpose) have
455   // been entered.
456   switch (this.accruedInputWithoutFormatting_.getLength()) {
457     case 0: // when the first few inputs are neither digits nor the plus sign.
458     case 1:
459     case 2:
460       return this.accruedInput_.toString();
461     case 3:
462       if (this.attemptToExtractIdd_()) {
463         this.isExpectingCountryCode_ = true;
464       } else {
465         // No IDD or plus sign is found, must be entering in national format.
466         this.removeNationalPrefixFromNationalNumber_();
467         return this.attemptToChooseFormattingPattern_();
468       }
469     case 4:
470     case 5:
471       if (this.isExpectingCountryCode_) {
472         if (this.attemptToExtractCountryCode_()) {
473           this.isExpectingCountryCode_ = false;
474         }
475         return this.prefixBeforeNationalNumber_.toString() +
476             this.nationalNumber_.toString();
477       }
478     // We make a last attempt to extract a country code at the 6th digit because
479     // the maximum length of IDD and country code are both 3.
480     case 6:
481       if (this.isExpectingCountryCode_ &&
482           !this.attemptToExtractCountryCode_()) {
483         this.ableToFormat_ = false;
484         return this.accruedInput_.toString();
485       }
486     default:
487       if (this.possibleFormats_.length > 0) {
488         // The formatting pattern is already chosen.
489         /** @type {string} */
490         var tempNationalNumber = this.inputDigitHelper_(nextChar);
491         // See if the accrued digits can be formatted properly already. If not,
492         // use the results from inputDigitHelper, which does formatting based on
493         // the formatting pattern chosen.
494         /** @type {string} */
495         var formattedNumber = this.attemptToFormatAccruedDigits_();
496         if (formattedNumber.length > 0) {
497           return formattedNumber;
498         }
499         this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
500         if (this.maybeCreateNewTemplate_()) {
501           return this.inputAccruedNationalNumber_();
502         }
503         return this.ableToFormat_ ?
504             this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
505             tempNationalNumber;
506       } else {
507         return this.attemptToChooseFormattingPattern_();
508       }
509   }
510 };
511
512
513 /**
514  * @return {string}
515  * @private
516  */
517 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
518     function() {
519
520   /** @type {string} */
521   var nationalNumber = this.nationalNumber_.toString();
522   /** @type {number} */
523   var possibleFormatsLength = this.possibleFormats_.length;
524   for (var i = 0; i < possibleFormatsLength; ++i) {
525     /** @type {i18n.phonenumbers.NumberFormat} */
526     var numFormat = this.possibleFormats_[i];
527     /** @type {string} */
528     var pattern = numFormat.getPatternOrDefault();
529     /** @type {RegExp} */
530     var patternRegExp = new RegExp('^(' + pattern + ')$');
531     if (patternRegExp.test(nationalNumber)) {
532       /** @type {string} */
533       var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
534                                                    numFormat.getFormat());
535       return this.prefixBeforeNationalNumber_.toString() + formattedNumber;
536     }
537   }
538   return '';
539 };
540
541
542 /**
543  * Returns the current position in the partially formatted phone number of the
544  * character which was previously passed in as the parameter of
545  * inputDigitAndRememberPosition().
546  *
547  * @return {number}
548  */
549 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
550     function() {
551
552   if (!this.ableToFormat_) {
553     return this.originalPosition_;
554   }
555   /** @type {number} */
556   var accruedInputIndex = 0;
557   /** @type {number} */
558   var currentOutputIndex = 0;
559   /** @type {string} */
560   var accruedInputWithoutFormatting =
561       this.accruedInputWithoutFormatting_.toString();
562   /** @type {string} */
563   var currentOutput = this.currentOutput_.toString();
564   /** @type {number} */
565   var currentOutputLength = currentOutput.length;
566   while (accruedInputIndex < this.positionToRemember_ &&
567          currentOutputIndex < currentOutputLength) {
568     if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
569         currentOutput.charAt(currentOutputIndex)) {
570       accruedInputIndex++;
571       currentOutputIndex++;
572     } else {
573       currentOutputIndex++;
574     }
575   }
576   return currentOutputIndex;
577 };
578
579
580 /**
581  * Attempts to set the formatting template and returns a string which contains
582  * the formatted version of the digits entered so far.
583  *
584  * @return {string}
585  * @private
586  */
587 i18n.phonenumbers.AsYouTypeFormatter.prototype.
588     attemptToChooseFormattingPattern_ = function() {
589
590   /** @type {string} */
591   var nationalNumber = this.nationalNumber_.toString();
592   // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
593   // digits of national number (excluding national prefix) have been entered.
594   if (nationalNumber.length >= this.MIN_LEADING_DIGITS_LENGTH_) {
595     this.getAvailableFormats_(
596         nationalNumber.substring(0, this.MIN_LEADING_DIGITS_LENGTH_));
597     this.maybeCreateNewTemplate_();
598     return this.inputAccruedNationalNumber_();
599   } else {
600     return this.prefixBeforeNationalNumber_.toString() + nationalNumber;
601   }
602 };
603
604
605 /**
606  * Invokes inputDigitHelper on each digit of the national number accrued, and
607  * returns a formatted string in the end.
608  *
609  * @return {string}
610  * @private
611  */
612 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
613     function() {
614
615   /** @type {string} */
616   var nationalNumber = this.nationalNumber_.toString();
617   /** @type {number} */
618   var lengthOfNationalNumber = nationalNumber.length;
619   if (lengthOfNationalNumber > 0) {
620     /** @type {string} */
621     var tempNationalNumber = '';
622     for (var i = 0; i < lengthOfNationalNumber; i++) {
623       tempNationalNumber =
624           this.inputDigitHelper_(nationalNumber.charAt(i));
625     }
626     return this.ableToFormat_ ?
627         this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
628         tempNationalNumber;
629   } else {
630     return this.prefixBeforeNationalNumber_.toString();
631   }
632 };
633
634
635 /**
636  * @private
637  */
638 i18n.phonenumbers.AsYouTypeFormatter.prototype.
639     removeNationalPrefixFromNationalNumber_ = function() {
640
641   /** @type {string} */
642   var nationalNumber = this.nationalNumber_.toString();
643   /** @type {number} */
644   var startOfNationalNumber = 0;
645   if (this.currentMetaData_.getCountryCode() == 1 &&
646       nationalNumber.charAt(0) == '1') {
647     startOfNationalNumber = 1;
648     this.prefixBeforeNationalNumber_.append('1 ');
649     this.isInternationalFormatting_ = true;
650   } else if (this.currentMetaData_.hasNationalPrefix()) {
651     /** @type {Array.<string>} */
652     var m = nationalNumber.match(this.nationalPrefixForParsing_);
653     if (m != null && m[0] != null && m[0].length > 0) {
654       // When the national prefix is detected, we use international formatting
655       // rules instead of national ones, because national formatting rules could
656       // contain local formatting rules for numbers entered without area code.
657       this.isInternationalFormatting_ = true;
658       startOfNationalNumber = m[0].length;
659       this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
660           startOfNationalNumber));
661     }
662   }
663   this.nationalNumber_.clear();
664   this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
665 };
666
667
668 /**
669  * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
670  * available, and places the remaining input into nationalNumber.
671  *
672  * @return {boolean} true when accruedInputWithoutFormatting begins with the
673  *     plus sign or valid IDD for defaultCountry.
674  * @private
675  */
676 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
677     function() {
678
679   /** @type {string} */
680   var accruedInputWithoutFormatting =
681       this.accruedInputWithoutFormatting_.toString();
682   /** @type {Array.<string>} */
683   var m = accruedInputWithoutFormatting.match(this.internationalPrefix_);
684   if (m != null && m[0] != null && m[0].length > 0) {
685     this.isInternationalFormatting_ = true;
686     /** @type {number} */
687     var startOfCountryCode = m[0].length;
688     this.nationalNumber_.clear();
689     this.nationalNumber_.append(
690         accruedInputWithoutFormatting.substring(startOfCountryCode));
691     this.prefixBeforeNationalNumber_.append(
692         accruedInputWithoutFormatting.substring(0, startOfCountryCode));
693     if (accruedInputWithoutFormatting.charAt(0) !=
694         i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
695       this.prefixBeforeNationalNumber_.append(' ');
696     }
697     return true;
698   }
699   return false;
700 };
701
702
703 /**
704  * Extracts country code from the beginning of nationalNumber to
705  * prefixBeforeNationalNumber when they are available, and places the remaining
706  * input into nationalNumber.
707  *
708  * @return {boolean} true when a valid country code can be found.
709  * @private
710  */
711 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractCountryCode_ =
712     function() {
713
714   if (this.nationalNumber_.getLength() == 0) {
715     return false;
716   }
717   /** @type {!goog.string.StringBuffer} */
718   var numberWithoutCountryCode = new goog.string.StringBuffer();
719   /** @type {number} */
720   var countryCode = this.phoneUtil_.extractCountryCode(
721       this.nationalNumber_, numberWithoutCountryCode);
722   if (countryCode == 0) {
723     return false;
724   } else {
725     this.nationalNumber_.clear();
726     this.nationalNumber_.append(numberWithoutCountryCode.toString());
727     /** @type {string} */
728     var newRegionCode =
729         this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
730     if (newRegionCode != this.defaultCountry_) {
731       this.initializeCountrySpecificInfo_(newRegionCode);
732     }
733     /** @type {string} */
734     var countryCodeString = '' + countryCode;
735     this.prefixBeforeNationalNumber_.append(countryCodeString).append(' ');
736   }
737   return true;
738 };
739
740
741 /**
742  * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
743  * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
744  * version of digits), it is first normalized to the ASCII version. The return
745  * value is nextChar itself, or its normalized version, if nextChar is a digit
746  * in non-ASCII format.
747  *
748  * @param {string} nextChar
749  * @param {boolean} rememberPosition
750  * @return {string}
751  * @private
752  */
753 i18n.phonenumbers.AsYouTypeFormatter.prototype.
754     normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
755                                                     rememberPosition) {
756
757   if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
758     this.accruedInputWithoutFormatting_.append(nextChar);
759   }
760   if (nextChar in i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS) {
761     nextChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
762     this.accruedInputWithoutFormatting_.append(nextChar);
763     this.nationalNumber_.append(nextChar);
764   }
765   if (rememberPosition) {
766     this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
767   }
768   return nextChar;
769 };
770
771
772 /**
773  * @param {string} nextChar
774  * @return {string}
775  * @private
776  */
777 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
778     function(nextChar) {
779
780   /** @type {string} */
781   var formattingTemplate = this.formattingTemplate_.toString();
782   if (formattingTemplate.substring(this.lastMatchPosition_)
783       .search(this.digitPattern_) >= 0) {
784     /** @type {number} */
785     var digitPatternStart = formattingTemplate.search(this.digitPattern_);
786     /** @type {string} */
787     var tempTemplate = formattingTemplate.replace(this.digitPattern_, nextChar);
788     this.formattingTemplate_.clear();
789     this.formattingTemplate_.append(tempTemplate);
790     this.lastMatchPosition_ = digitPatternStart;
791     return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
792   } else {
793     if (this.possibleFormats_.length == 1) {
794       // More digits are entered than we could handle, and there are no other
795       // valid patterns to try.
796       this.ableToFormat_ = false;
797     }  // else, we just reset the formatting pattern.
798     this.currentFormattingPattern_ = '';
799     return this.accruedInput_.toString();
800   }
801 };