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