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