Add comment
[platform/upstream/libphonenumber.git] / javascript / i18n / phonenumbers / asyoutypeformatter.js
index 1a94428..53ee5a3 100644 (file)
@@ -1,6 +1,6 @@
-/*
+/**
  * @license
- * Copyright (C) 2010 Google Inc.
+ * Copyright (C) 2010 The Libphonenumber Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * @fileoverview  A formatter which formats phone numbers as they are entered.
  * (based on the java implementation).
  *
- * An AsYouTypeFormatter could be created by new AsYouTypeFormatter(). After
- * that digits could be added by invoking the inputDigit method on the formatter
+ * <p>An AsYouTypeFormatter can be created by new AsYouTypeFormatter(). After
+ * that, digits can be added by invoking {@link #inputDigit} on the formatter
  * instance, and the partially formatted phone number will be returned each time
- * a digit is added. The clear method should be invoked before a new number
- * needs to be formatted.
+ * a digit is added. {@link #clear} can be invoked before formatting a new
+ * number.
  *
- * See testAsYouTypeFormatterUS(), testAsYouTestFormatterGB() and
- * testAsYouTypeFormatterDE() in asyoutypeformatter_test.js for more details
- * on how the formatter is to be used.
+ * <p>See the unittests for more details on how the formatter is to be used.
  *
  * @author Nikolaos Trogkanis
  */
@@ -44,58 +42,29 @@ goog.require('i18n.phonenumbers.PhoneNumberDesc');
 goog.require('i18n.phonenumbers.PhoneNumberUtil');
 goog.require('i18n.phonenumbers.metadata');
 
+
+
 /**
- * Constructs a light-weight formatter which does no formatting, but outputs
- * exactly what is fed into the inputDigit method.
+ * Constructs an AsYouTypeFormatter for the specific region.
  *
- * @param {string} regionCode the country/region where the phone number is being
- *     entered.
+ * @param {string} regionCode the ISO 3166-1 two-letter region code that denotes
+ *     the region where the phone number is being entered.
  * @constructor
  */
 i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
   /**
-   * A pattern that is used to match character classes in regular expressions.
-   * An example of a character class is [1-4].
-   * @const
-   * @type {RegExp}
-   * @private
-   */
-  this.CHARACTER_CLASS_PATTERN_ = /\[([^\[\]])*\]/g;
-  /**
-   * Any digit in a regular expression that actually denotes a digit. For
-   * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
-   * (8 and 0) are standalone digits, but the rest are not.
-   * Two look-aheads are needed because the number following \\d could be a
-   * two-digit number, since the phone number can be as long as 15 digits.
-   * @const
-   * @type {RegExp}
-   * @private
-   */
-  this.STANDALONE_DIGIT_PATTERN_ = /\d(?=[^,}][^,}])/g;
-  /**
-   * This is the minimum length of national number accrued that is required to
-   * trigger the formatter. The first element of the leadingDigitsPattern of
-   * each numberFormat contains a regular expression that matches up to this
-   * number of digits.
-   * @const
-   * @type {number}
-   * @private
-   */
-  this.MIN_LEADING_DIGITS_LENGTH_ = 3;
-  /**
    * The digits that have not been entered yet will be represented by a \u2008,
    * the punctuation space.
    * @const
    * @type {string}
    * @private
    */
-  this.digitPlaceholder_ = '\u2008';
+  this.DIGIT_PLACEHOLDER_ = '\u2008';
   /**
    * @type {RegExp}
    * @private
    */
-  this.digitPattern_ = new RegExp(this.digitPlaceholder_);
-
+  this.DIGIT_PATTERN_ = new RegExp(this.DIGIT_PLACEHOLDER_);
   /**
    * @type {string}
    * @private
@@ -124,20 +93,33 @@ i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
    */
   this.accruedInputWithoutFormatting_ = new goog.string.StringBuffer();
   /**
+   * This indicates whether AsYouTypeFormatter is currently doing the
+   * formatting.
    * @type {boolean}
    * @private
    */
   this.ableToFormat_ = true;
   /**
+   * Set to true when users enter their own formatting. AsYouTypeFormatter will
+   * do no formatting at all when this is set to true.
+   * @type {boolean}
+   * @private
+   */
+  this.inputHasFormatting_ = false;
+  /**
+   * This is set to true when we know the user is entering a full national
+   * significant number, since we have either detected a national prefix or an
+   * international dialing prefix. When this is true, we will no longer use
+   * local number formatting patterns.
    * @type {boolean}
    * @private
    */
-  this.isInternationalFormatting_ = false;
+  this.isCompleteNumber_ = false;
   /**
    * @type {boolean}
    * @private
    */
-  this.isExpectingCountryCode_ = false;
+  this.isExpectingCountryCallingCode_ = false;
   /**
    * @type {i18n.phonenumbers.PhoneNumberUtil}
    * @private
@@ -165,11 +147,26 @@ i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
    */
   this.positionToRemember_ = 0;
   /**
+   * This contains anything that has been entered so far preceding the national
+   * significant number, and it is formatted (e.g. with space inserted). For
+   * example, this can contain IDD, country code, and/or NDD, etc.
    * @type {!goog.string.StringBuffer}
    * @private
    */
   this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
   /**
+   * @type {boolean}
+   * @private
+   */
+  this.shouldAddSpaceAfterNationalPrefix_ = false;
+  /**
+   * This contains the national prefix that has been extracted. It contains only
+   * digits without formatting.
+   * @type {string}
+   * @private
+   */
+  this.extractedNationalPrefix_ = '';
+  /**
    * @type {!goog.string.StringBuffer}
    * @private
    */
@@ -179,44 +176,141 @@ i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
    * @private
    */
   this.possibleFormats_ = [];
-
   /**
-   *  @type {string}
+   * @type {string}
    * @private
    */
   this.defaultCountry_ = regionCode;
-  this.initializeCountrySpecificInfo_(this.defaultCountry_);
+  this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
   /**
    * @type {i18n.phonenumbers.PhoneMetadata}
    * @private
    */
-  this.defaultMetaData_ = this.currentMetaData_;
+  this.defaultMetadata_ = this.currentMetadata_;
 };
 
+
+/**
+ * Character used when appropriate to separate a prefix, such as a long NDD or a
+ * country calling code, from the national number.
+ * @const
+ * @type {string}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ = ' ';
+
+
+/**
+ * @const
+ * @type {i18n.phonenumbers.PhoneMetadata}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
+    new i18n.phonenumbers.PhoneMetadata();
+i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
+    .setInternationalPrefix('NA');
+
+
+/**
+ * A pattern that is used to match character classes in regular expressions.
+ * An example of a character class is [1-4].
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
+    /\[([^\[\]])*\]/g;
+
+
+/**
+ * Any digit in a regular expression that actually denotes a digit. For
+ * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
+ * (8 and 0) are standalone digits, but the rest are not.
+ * Two look-aheads are needed because the number following \\d could be a
+ * two-digit number, since the phone number can be as long as 15 digits.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
+    /\d(?=[^,}][^,}])/g;
+
+
+/**
+ * A pattern that is used to determine if a numberFormat under availableFormats
+ * is eligible to be used by the AYTF. It is eligible when the format element
+ * under numberFormat contains groups of the dollar sign followed by a single
+ * digit, separated by valid phone number punctuation. This prevents invalid
+ * punctuation (such as the star sign in Israeli star numbers) getting into the
+ * output of the AYTF.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
+    '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
+    '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
+
+
+/**
+ * A set of characters that, if found in a national prefix formatting rules, are
+ * an indicator to us that we should separate the national prefix from the
+ * number when formatting.
+ * @const
+ * @type {RegExp}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.NATIONAL_PREFIX_SEPARATORS_PATTERN_ =
+    /[- ]/;
+
+
 /**
- * @param {string} regionCode
+ * This is the minimum length of national number accrued that is required to
+ * trigger the formatter. The first element of the leadingDigitsPattern of
+ * each numberFormat contains a regular expression that matches up to this
+ * number of digits.
+ * @const
+ * @type {number}
  * @private
  */
-i18n.phonenumbers.AsYouTypeFormatter.prototype.initializeCountrySpecificInfo_ =
-  function(regionCode) {
+i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
+
 
+/**
+ * The metadata needed by this class is the same for all regions sharing the
+ * same country calling code. Therefore, we return the metadata for "main"
+ * region for this country calling code.
+ * @param {string} regionCode an ISO 3166-1 two-letter region code.
+ * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
+    function(regionCode) {
+
+  /** @type {number} */
+  var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
+  /** @type {string} */
+  var mainCountry =
+      this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
   /** @type {i18n.phonenumbers.PhoneMetadata} */
-  this.currentMetaData_ = this.phoneUtil_.getMetadataForRegion(regionCode);
-  /** @type {RegExp} */
-  this.nationalPrefixForParsing_ = new RegExp('^(' + this.currentMetaData_
-      .getNationalPrefixForParsing() + ')');
-  /** @type {RegExp} */
-  this.internationalPrefix_ = new RegExp('^(' + '\\+|' +
-      this.currentMetaData_.getInternationalPrefix() + ')');
+  var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
+  if (metadata != null) {
+    return metadata;
+  }
+  // Set to a default instance of the metadata. This allows us to function with
+  // an incorrect region code, even if formatting only works for numbers
+  // specified with '+'.
+  return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
 };
 
+
 /**
  * @return {boolean} true if a new template is created as opposed to reusing the
  *     existing template.
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
-  function() {
+    function() {
 
   // When there are multiple available formats, the formatter uses the first
   // format where a formatting template could be created.
@@ -232,6 +326,13 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
     }
     if (this.createFormattingTemplate_(numberFormat)) {
       this.currentFormattingPattern_ = pattern;
+      this.shouldAddSpaceAfterNationalPrefix_ =
+          i18n.phonenumbers.AsYouTypeFormatter.
+          NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
+              numberFormat.getNationalPrefixFormattingRule());
+      // With a new formatting template, the matched position using the old
+      // template needs to be reset.
+      this.lastMatchPosition_ = 0;
       return true;
     }
   }
@@ -239,67 +340,99 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
   return false;
 };
 
+
 /**
- * @param {string} leadingThreeDigits
+ * @param {string} leadingDigits leading digits of entered number.
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
-  function(leadingThreeDigits) {
+    function(leadingDigits) {
 
   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
-  var formatList = (this.isInternationalFormatting_ && this.currentMetaData_
-      .intlNumberFormatCount() > 0) ? this.currentMetaData_
-      .intlNumberFormatArray() : this.currentMetaData_.numberFormatArray();
-  this.possibleFormats_ = formatList;
-  this.narrowDownPossibleFormats_(leadingThreeDigits);
+  var formatList =
+      (this.isCompleteNumber_ &&
+           this.currentMetadata_.intlNumberFormatCount() > 0) ?
+      this.currentMetadata_.intlNumberFormatArray() :
+      this.currentMetadata_.numberFormatArray();
+  /** @type {number} */
+  var formatListLength = formatList.length;
+  for (var i = 0; i < formatListLength; ++i) {
+    /** @type {i18n.phonenumbers.NumberFormat} */
+    var format = formatList[i];
+    /** @type {boolean} */
+    var nationalPrefixIsUsedByCountry =
+        this.currentMetadata_.hasNationalPrefix();
+    if (!nationalPrefixIsUsedByCountry || this.isCompleteNumber_ ||
+        format.getNationalPrefixOptionalWhenFormatting() ||
+        this.phoneUtil_.formattingRuleHasFirstGroupOnly(
+            format.getNationalPrefixFormattingRuleOrDefault())) {
+      if (this.isFormatEligible_(format.getFormatOrDefault())) {
+        this.possibleFormats_.push(format);
+      }
+    }
+  }
+  this.narrowDownPossibleFormats_(leadingDigits);
 };
 
+
+/**
+ * @param {string} format
+ * @return {boolean}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
+    function(format) {
+  return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
+      .test(format);
+};
+
+
 /**
  * @param {string} leadingDigits
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
-  function(leadingDigits) {
+    function(leadingDigits) {
 
   /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
   var possibleFormats = [];
   /** @type {number} */
-  var lengthOfLeadingDigits = leadingDigits.length;
-  /** @type {number} */
   var indexOfLeadingDigitsPattern =
-      lengthOfLeadingDigits - this.MIN_LEADING_DIGITS_LENGTH_;
+      leadingDigits.length -
+      i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
   /** @type {number} */
   var possibleFormatsLength = this.possibleFormats_.length;
   for (var i = 0; i < possibleFormatsLength; ++i) {
     /** @type {i18n.phonenumbers.NumberFormat} */
     var format = this.possibleFormats_[i];
-    if (format.leadingDigitsPatternCount() > indexOfLeadingDigitsPattern) {
-      /** @type {RegExp} */
-      var leadingDigitsPattern = new RegExp('^(' +
-          format.getLeadingDigitsPattern(indexOfLeadingDigitsPattern) + ')');
-      if (leadingDigitsPattern.test(leadingDigits)) {
-        possibleFormats.push(this.possibleFormats_[i]);
-      }
-    } else {
-      // else the particular format has no more specific leadingDigitsPattern,
-      // and it should be retained.
+    if (format.leadingDigitsPatternCount() == 0) {
+      // Keep everything that isn't restricted by leading digits.
+      possibleFormats.push(this.possibleFormats_[i]);
+      continue;
+    }
+    /** @type {number} */
+    var lastLeadingDigitsPattern = Math.min(
+        indexOfLeadingDigitsPattern, format.leadingDigitsPatternCount() - 1);
+    /** @type {string} */
+    var leadingDigitsPattern = /** @type {string} */
+        (format.getLeadingDigitsPattern(lastLeadingDigitsPattern));
+    if (leadingDigits.search(leadingDigitsPattern) == 0) {
       possibleFormats.push(this.possibleFormats_[i]);
     }
   }
   this.possibleFormats_ = possibleFormats;
 };
 
+
 /**
  * @param {i18n.phonenumbers.NumberFormat} format
  * @return {boolean}
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
-  function(format) {
+    function(format) {
 
   /** @type {string} */
-  var numberFormat = format.getFormatOrDefault();
-  /** @type {string} */
   var numberPattern = format.getPatternOrDefault();
 
   // The formatter doesn't format numbers when numberPattern contains '|', e.g.
@@ -309,18 +442,26 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
   }
 
   // Replace anything in the form of [..] with \d
-  numberPattern = numberPattern.replace(this.CHARACTER_CLASS_PATTERN_, '\\d');
+  numberPattern = numberPattern.replace(
+      i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
 
   // Replace any standalone digit (not the one in d{}) with \d
-  numberPattern = numberPattern.replace(this.STANDALONE_DIGIT_PATTERN_, '\\d');
+  numberPattern = numberPattern.replace(
+      i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
   this.formattingTemplate_.clear();
-  this.formattingTemplate_.append(this.getFormattingTemplate_(numberPattern,
-      numberFormat));
-  return true;
+  /** @type {string} */
+  var tempTemplate = this.getFormattingTemplate_(numberPattern,
+                                                 format.getFormatOrDefault());
+  if (tempTemplate.length > 0) {
+    this.formattingTemplate_.append(tempTemplate);
+    return true;
+  }
+  return false;
 };
 
+
 /**
- * Gets a formatting template which could be used to efficiently format a
+ * Gets a formatting template which can be used to efficiently format a
  * partial number where digits are added one by one.
  *
  * @param {string} numberPattern
@@ -329,7 +470,7 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
-  function(numberPattern, numberFormat) {
+    function(numberPattern, numberFormat) {
 
   // Creates a phone number consisting only of the digit 9 that matches the
   // numberPattern by applying the pattern to the longestPhoneNumber string.
@@ -340,17 +481,23 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
   // this match will always succeed
   /** @type {string} */
   var aPhoneNumber = m[0];
+  // No formatting template can be created if the number of digits entered so
+  // far is longer than the maximum the current formatting rule can accommodate.
+  if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
+    return '';
+  }
   // Formats the number according to numberFormat
   /** @type {string} */
   var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
                                       numberFormat);
-  // Replaces each digit with character digitPlaceholder
-  template = template.replace(new RegExp('9', 'g'), this.digitPlaceholder_);
+  // Replaces each digit with character DIGIT_PLACEHOLDER
+  template = template.replace(new RegExp('9', 'g'), this.DIGIT_PLACEHOLDER_);
   return template;
 };
 
+
 /**
- * Clears the internal state of the formatter, so it could be reused.
+ * Clears the internal state of the formatter, so it can be reused.
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
   this.currentOutput_ = '';
@@ -360,23 +507,30 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
   this.lastMatchPosition_ = 0;
   this.currentFormattingPattern_ = '';
   this.prefixBeforeNationalNumber_.clear();
+  this.extractedNationalPrefix_ = '';
   this.nationalNumber_.clear();
   this.ableToFormat_ = true;
+  this.inputHasFormatting_ = false;
   this.positionToRemember_ = 0;
   this.originalPosition_ = 0;
-  this.isInternationalFormatting_ = false;
-  this.isExpectingCountryCode_ = false;
+  this.isCompleteNumber_ = false;
+  this.isExpectingCountryCallingCode_ = false;
   this.possibleFormats_ = [];
-  if (this.currentMetaData_ != this.defaultMetaData_) {
-    this.initializeCountrySpecificInfo_(this.defaultCountry_);
+  this.shouldAddSpaceAfterNationalPrefix_ = false;
+  if (this.currentMetadata_ != this.defaultMetadata_) {
+    this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
   }
 };
 
+
 /**
  * Formats a phone number on-the-fly as each digit is entered.
  *
  * @param {string} nextChar the most recently entered digit of a phone number.
- *     Formatting characters are allowed, but they are removed from the result.
+ *     Formatting characters are allowed, but as soon as they are encountered
+ *     this method formats the number as entered and not 'as you type' anymore.
+ *     Full width digits and Arabic-indic digits are allowed, and will be shown
+ *     as they are.
  * @return {string} the partially formatted phone number.
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
@@ -385,23 +539,26 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
   return this.currentOutput_;
 };
 
+
 /**
- * Same as inputDigit, but remembers the position where nextChar is inserted, so
- * that it could be retrieved later by using getRememberedPosition(). The
- * remembered position will be automatically adjusted if additional formatting
- * characters are later inserted/removed in front of nextChar.
+ * Same as {@link #inputDigit}, but remembers the position where
+ * {@code nextChar} is inserted, so that it can be retrieved later by using
+ * {@link #getRememberedPosition}. The remembered position will be automatically
+ * adjusted if additional formatting characters are later inserted/removed in
+ * front of {@code nextChar}.
  *
  * @param {string} nextChar
  * @return {string}
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
-  function(nextChar) {
+    function(nextChar) {
 
   this.currentOutput_ =
       this.inputDigitWithOptionToRememberPosition_(nextChar, true);
   return this.currentOutput_;
 };
 
+
 /**
  * @param {string} nextChar
  * @param {boolean} rememberPosition
@@ -417,83 +574,174 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
     this.originalPosition_ = this.accruedInput_.getLength();
   }
   // We do formatting on-the-fly only when each character entered is either a
-  // plus sign or a digit.
-  if (!i18n.phonenumbers.PhoneNumberUtil.VALID_START_CHAR_PATTERN
-      .test(nextChar)) {
+  // digit, or a plus sign (accepted at the start of the number only).
+  if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
     this.ableToFormat_ = false;
+    this.inputHasFormatting_ = true;
+  } else {
+    nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
+                                                         rememberPosition);
   }
   if (!this.ableToFormat_) {
+    // When we are unable to format because of reasons other than that
+    // formatting chars have been entered, it can be due to really long IDDs or
+    // NDDs. If that is the case, we might be able to do formatting again after
+    // extracting them.
+    if (this.inputHasFormatting_) {
+      return this.accruedInput_.toString();
+    } else if (this.attemptToExtractIdd_()) {
+      if (this.attemptToExtractCountryCallingCode_()) {
+        return this.attemptToChoosePatternWithPrefixExtracted_();
+      }
+    } else if (this.ableToExtractLongerNdd_()) {
+      // Add an additional space to separate long NDD and national significant
+      // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
+      // to true, since we don't want this to change later when we choose
+      // formatting templates.
+      this.prefixBeforeNationalNumber_.append(
+          i18n.phonenumbers.AsYouTypeFormatter.
+          SEPARATOR_BEFORE_NATIONAL_NUMBER_);
+      return this.attemptToChoosePatternWithPrefixExtracted_();
+    }
     return this.accruedInput_.toString();
   }
 
-  nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
-                                                       rememberPosition);
-
   // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
   // digits (the plus sign is counted as a digit as well for this purpose) have
   // been entered.
   switch (this.accruedInputWithoutFormatting_.getLength()) {
-  case 0: // this is the case where the first few inputs are neither digits nor
-          // the plus sign.
-  case 1:
-  case 2:
-    return this.accruedInput_.toString();
-  case 3:
-    if (this.attemptToExtractIdd_()) {
-      this.isExpectingCountryCode_ = true;
-    } else {
-      // No IDD or plus sign is found, must be entering in national format.
-      this.removeNationalPrefixFromNationalNumber_();
-      return this.attemptToChooseFormattingPattern_();
-    }
-  case 4:
-  case 5:
-    if (this.isExpectingCountryCode_) {
-      if (this.attemptToExtractCountryCode_()) {
-        this.isExpectingCountryCode_ = false;
-      }
-      return this.prefixBeforeNationalNumber_.toString() +
-          this.nationalNumber_.toString();
-    }
-  // We make a last attempt to extract a country code at the 6th digit because
-  // the maximum length of IDD and country code are both 3.
-  case 6:
-    if (this.isExpectingCountryCode_ && !this.attemptToExtractCountryCode_()) {
-      this.ableToFormat_ = false;
+    case 0:
+    case 1:
+    case 2:
       return this.accruedInput_.toString();
-    }
-  default:
-    if (this.possibleFormats_.length > 0) {
-      // The formatting pattern is already chosen.
-      /** @type {string} */
-      var tempNationalNumber = this.inputDigitHelper_(nextChar);
-      // See if the accrued digits can be formatted properly already. If not,
-      // use the results from inputDigitHelper, which does formatting based on
-      // the formatting pattern chosen.
-      /** @type {string} */
-      var formattedNumber = this.attemptToFormatAccruedDigits_();
-      if (formattedNumber.length > 0) {
-        return formattedNumber;
+    case 3:
+      if (this.attemptToExtractIdd_()) {
+        this.isExpectingCountryCallingCode_ = true;
+      } else {
+        // No IDD or plus sign is found, might be entering in national format.
+        this.extractedNationalPrefix_ =
+            this.removeNationalPrefixFromNationalNumber_();
+        return this.attemptToChooseFormattingPattern_();
       }
-      this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
-      if (this.maybeCreateNewTemplate_()) {
-        return this.inputAccruedNationalNumber_();
+    default:
+      if (this.isExpectingCountryCallingCode_) {
+        if (this.attemptToExtractCountryCallingCode_()) {
+          this.isExpectingCountryCallingCode_ = false;
+        }
+        return this.prefixBeforeNationalNumber_.toString() +
+            this.nationalNumber_.toString();
+      }
+      if (this.possibleFormats_.length > 0) {
+        // The formatting patterns are already chosen.
+        /** @type {string} */
+        var tempNationalNumber = this.inputDigitHelper_(nextChar);
+        // See if the accrued digits can be formatted properly already. If not,
+        // use the results from inputDigitHelper, which does formatting based on
+        // the formatting pattern chosen.
+        /** @type {string} */
+        var formattedNumber = this.attemptToFormatAccruedDigits_();
+        if (formattedNumber.length > 0) {
+          return formattedNumber;
+        }
+        this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
+        if (this.maybeCreateNewTemplate_()) {
+          return this.inputAccruedNationalNumber_();
+        }
+        return this.ableToFormat_ ?
+            this.appendNationalNumber_(tempNationalNumber) :
+            this.accruedInput_.toString();
+      } else {
+        return this.attemptToChooseFormattingPattern_();
       }
-      return this.ableToFormat_ ?
-          this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
-          tempNationalNumber;
-    } else {
-      return this.attemptToChooseFormattingPattern_();
-    }
   }
 };
 
+
 /**
  * @return {string}
  * @private
  */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.
+    attemptToChoosePatternWithPrefixExtracted_ = function() {
+
+  this.ableToFormat_ = true;
+  this.isExpectingCountryCallingCode_ = false;
+  this.possibleFormats_ = [];
+  this.lastMatchPosition_ = 0;
+  this.formattingTemplate_.clear();
+  this.currentFormattingPattern_ = '';
+  return this.attemptToChooseFormattingPattern_();
+};
+
+
+/**
+ * @return {string}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.getExtractedNationalPrefix_ =
+    function() {
+  return this.extractedNationalPrefix_;
+};
+
+
+/**
+ * Some national prefixes are a substring of others. If extracting the shorter
+ * NDD doesn't result in a number we can format, we try to see if we can extract
+ * a longer version here.
+ * @return {boolean}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
+    function() {
+  if (this.extractedNationalPrefix_.length > 0) {
+    // Put the extracted NDD back to the national number before attempting to
+    // extract a new NDD.
+    /** @type {string} */
+    var nationalNumberStr = this.nationalNumber_.toString();
+    this.nationalNumber_.clear();
+    this.nationalNumber_.append(this.extractedNationalPrefix_);
+    this.nationalNumber_.append(nationalNumberStr);
+    // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
+    // cannot simply set it to empty string because people sometimes incorrectly
+    // enter national prefix after the country code, e.g. +44 (0)20-1234-5678.
+    /** @type {string} */
+    var prefixBeforeNationalNumberStr =
+        this.prefixBeforeNationalNumber_.toString();
+    /** @type {number} */
+    var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
+        this.extractedNationalPrefix_);
+    this.prefixBeforeNationalNumber_.clear();
+    this.prefixBeforeNationalNumber_.append(
+        prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
+  }
+  return this.extractedNationalPrefix_ !=
+      this.removeNationalPrefixFromNationalNumber_();
+};
+
+
+/**
+ * @param {string} nextChar
+ * @return {boolean}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
+    function(nextChar) {
+  return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
+      .test(nextChar) ||
+      (this.accruedInput_.getLength() == 1 &&
+       i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
+};
+
+
+/**
+ * Check to see if there is an exact pattern match for these digits. If so, we
+ * should use this instead of any other formatting template whose
+ * leadingDigitsPattern also matches the input.
+ * @return {string}
+ * @private
+ */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
-  function() {
+    function() {
 
   /** @type {string} */
   var nationalNumber = this.nationalNumber_.toString();
@@ -501,30 +749,66 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
   var possibleFormatsLength = this.possibleFormats_.length;
   for (var i = 0; i < possibleFormatsLength; ++i) {
     /** @type {i18n.phonenumbers.NumberFormat} */
-    var numFormat = this.possibleFormats_[i];
+    var numberFormat = this.possibleFormats_[i];
     /** @type {string} */
-    var pattern = numFormat.getPatternOrDefault();
+    var pattern = numberFormat.getPatternOrDefault();
     /** @type {RegExp} */
-    var patternRegExp = new RegExp('^(' + pattern + ')$');
+    var patternRegExp = new RegExp('^(?:' + pattern + ')$');
     if (patternRegExp.test(nationalNumber)) {
+      this.shouldAddSpaceAfterNationalPrefix_ =
+          i18n.phonenumbers.AsYouTypeFormatter.
+          NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
+              numberFormat.getNationalPrefixFormattingRule());
       /** @type {string} */
       var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
-                                                   numFormat.getFormat());
-      return this.prefixBeforeNationalNumber_.toString() + formattedNumber;
+                                                   numberFormat.getFormat());
+      return this.appendNationalNumber_(formattedNumber);
     }
   }
   return '';
 };
 
+
+/**
+ * Combines the national number with any prefix (IDD/+ and country code or
+ * national prefix) that was collected. A space will be inserted between them if
+ * the current formatting template indicates this to be suitable.
+ * @param {string} nationalNumber The number to be appended.
+ * @return {string} The combined number.
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.appendNationalNumber_ =
+    function(nationalNumber) {
+  /** @type {number} */
+  var prefixBeforeNationalNumberLength =
+      this.prefixBeforeNationalNumber_.getLength();
+  if (this.shouldAddSpaceAfterNationalPrefix_ &&
+      prefixBeforeNationalNumberLength > 0 &&
+      this.prefixBeforeNationalNumber_.toString().charAt(
+          prefixBeforeNationalNumberLength - 1) !=
+      i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_) {
+    // We want to add a space after the national prefix if the national prefix
+    // formatting rule indicates that this would normally be done, with the
+    // exception of the case where we already appended a space because the NDD
+    // was surprisingly long.
+    return this.prefixBeforeNationalNumber_ +
+        i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ +
+        nationalNumber;
+  } else {
+    return this.prefixBeforeNationalNumber_ + nationalNumber;
+  }
+};
+
+
 /**
  * Returns the current position in the partially formatted phone number of the
  * character which was previously passed in as the parameter of
- * inputDigitAndRememberPosition().
+ * {@link #inputDigitAndRememberPosition}.
  *
  * @return {number}
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
-  function() {
+    function() {
 
   if (!this.ableToFormat_) {
     return this.originalPosition_;
@@ -538,18 +822,18 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
       this.accruedInputWithoutFormatting_.toString();
   /** @type {string} */
   var currentOutput = this.currentOutput_.toString();
-  while (accruedInputIndex < this.positionToRemember_) {
+  while (accruedInputIndex < this.positionToRemember_ &&
+         currentOutputIndex < currentOutput.length) {
     if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
         currentOutput.charAt(currentOutputIndex)) {
       accruedInputIndex++;
-      currentOutputIndex++;
-    } else {
-      currentOutputIndex++;
     }
+    currentOutputIndex++;
   }
   return currentOutputIndex;
 };
 
+
 /**
  * Attempts to set the formatting template and returns a string which contains
  * the formatted version of the digits entered so far.
@@ -562,18 +846,24 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
 
   /** @type {string} */
   var nationalNumber = this.nationalNumber_.toString();
-  // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
+  // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
   // digits of national number (excluding national prefix) have been entered.
-  if (nationalNumber.length >= this.MIN_LEADING_DIGITS_LENGTH_) {
-    this.getAvailableFormats_(
-        nationalNumber.substring(0, this.MIN_LEADING_DIGITS_LENGTH_));
-    this.maybeCreateNewTemplate_();
-    return this.inputAccruedNationalNumber_();
+  if (nationalNumber.length >=
+      i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
+    this.getAvailableFormats_(nationalNumber);
+    // See if the accrued digits can be formatted properly already.
+    var formattedNumber = this.attemptToFormatAccruedDigits_();
+    if (formattedNumber.length > 0) {
+      return formattedNumber;
+    }
+    return this.maybeCreateNewTemplate_() ?
+        this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
   } else {
-    return this.prefixBeforeNationalNumber_.toString() + nationalNumber;
+    return this.appendNationalNumber_(nationalNumber);
   }
 };
 
+
 /**
  * Invokes inputDigitHelper on each digit of the national number accrued, and
  * returns a formatted string in the end.
@@ -582,7 +872,7 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
-  function() {
+    function() {
 
   /** @type {string} */
   var nationalNumber = this.nationalNumber_.toString();
@@ -596,36 +886,67 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
           this.inputDigitHelper_(nationalNumber.charAt(i));
     }
     return this.ableToFormat_ ?
-        this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
-        tempNationalNumber;
+        this.appendNationalNumber_(tempNationalNumber) :
+        this.accruedInput_.toString();
   } else {
     return this.prefixBeforeNationalNumber_.toString();
   }
 };
 
+
 /**
+ * @return {boolean} true if the current country is a NANPA country and the
+ *     national number begins with the national prefix.
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.
-      removeNationalPrefixFromNationalNumber_ = function() {
+    isNanpaNumberWithNationalPrefix_ = function() {
+  // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
+  // prefix. The reason is that national significant numbers in NANPA always
+  // start with [2-9] after the national prefix. Numbers beginning with 1[01]
+  // can only be short/emergency numbers, which don't need the national prefix.
+  if (this.currentMetadata_.getCountryCode() != 1) {
+    return false;
+  }
+  /** @type {string} */
+  var nationalNumber = this.nationalNumber_.toString();
+  return (nationalNumber.charAt(0) == '1') &&
+      (nationalNumber.charAt(1) != '0') &&
+      (nationalNumber.charAt(1) != '1');
+};
+
+
+/**
+ * Returns the national prefix extracted, or an empty string if it is not
+ * present.
+ * @return {string}
+ * @private
+ */
+i18n.phonenumbers.AsYouTypeFormatter.prototype.
+    removeNationalPrefixFromNationalNumber_ = function() {
 
   /** @type {string} */
   var nationalNumber = this.nationalNumber_.toString();
   /** @type {number} */
   var startOfNationalNumber = 0;
-  if (this.currentMetaData_.getCountryCode() == 1 &&
-      nationalNumber.charAt(0) == '1') {
+  if (this.isNanpaNumberWithNationalPrefix_()) {
     startOfNationalNumber = 1;
-    this.prefixBeforeNationalNumber_.append('1 ');
-    this.isInternationalFormatting_ = true;
-  } else if (this.currentMetaData_.hasNationalPrefix()) {
+    this.prefixBeforeNationalNumber_.append('1').append(
+        i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
+    this.isCompleteNumber_ = true;
+  } else if (this.currentMetadata_.hasNationalPrefixForParsing()) {
+    /** @type {RegExp} */
+    var nationalPrefixForParsing = new RegExp(
+        '^(?:' + this.currentMetadata_.getNationalPrefixForParsing() + ')');
     /** @type {Array.<string>} */
-    var m = nationalNumber.match(this.nationalPrefixForParsing_);
+    var m = nationalNumber.match(nationalPrefixForParsing);
+    // Since some national prefix patterns are entirely optional, check that a
+    // national prefix could actually be extracted.
     if (m != null && m[0] != null && m[0].length > 0) {
       // When the national prefix is detected, we use international formatting
       // rules instead of national ones, because national formatting rules could
       // contain local formatting rules for numbers entered without area code.
-      this.isInternationalFormatting_ = true;
+      this.isCompleteNumber_ = true;
       startOfNationalNumber = m[0].length;
       this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
           startOfNationalNumber));
@@ -633,8 +954,10 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
   }
   this.nationalNumber_.clear();
   this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
+  return nationalNumber.substring(0, startOfNationalNumber);
 };
 
+
 /**
  * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
  * available, and places the remaining input into nationalNumber.
@@ -644,74 +967,90 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
-  function() {
+    function() {
 
   /** @type {string} */
   var accruedInputWithoutFormatting =
       this.accruedInputWithoutFormatting_.toString();
+  /** @type {RegExp} */
+  var internationalPrefix = new RegExp(
+      '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
+      this.currentMetadata_.getInternationalPrefix() + ')');
   /** @type {Array.<string>} */
-  var m = accruedInputWithoutFormatting.match(this.internationalPrefix_);
+  var m = accruedInputWithoutFormatting.match(internationalPrefix);
   if (m != null && m[0] != null && m[0].length > 0) {
-    this.isInternationalFormatting_ = true;
+    this.isCompleteNumber_ = true;
     /** @type {number} */
-    var startOfCountryCode = m[0].length;
+    var startOfCountryCallingCode = m[0].length;
     this.nationalNumber_.clear();
     this.nationalNumber_.append(
-        accruedInputWithoutFormatting.substring(startOfCountryCode));
+        accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
+    this.prefixBeforeNationalNumber_.clear();
     this.prefixBeforeNationalNumber_.append(
-        accruedInputWithoutFormatting.substring(0, startOfCountryCode));
+        accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
     if (accruedInputWithoutFormatting.charAt(0) !=
         i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
-      this.prefixBeforeNationalNumber_.append(' ');
+      this.prefixBeforeNationalNumber_.append(
+          i18n.phonenumbers.AsYouTypeFormatter.
+          SEPARATOR_BEFORE_NATIONAL_NUMBER_);
     }
     return true;
   }
   return false;
 };
 
+
 /**
- * Extracts country code from the beginning of nationalNumber to
+ * Extracts the country calling code from the beginning of nationalNumber to
  * prefixBeforeNationalNumber when they are available, and places the remaining
  * input into nationalNumber.
  *
- * @return {boolean} true when a valid country code can be found.
+ * @return {boolean} true when a valid country calling code can be found.
  * @private
  */
-i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractCountryCode_ =
-  function() {
+i18n.phonenumbers.AsYouTypeFormatter.prototype.
+    attemptToExtractCountryCallingCode_ = function() {
 
-    if (this.nationalNumber_.getLength() == 0) {
-      return false;
-    }
-    /** @type {!goog.string.StringBuffer} */
-    var numberWithoutCountryCode = new goog.string.StringBuffer();
-    /** @type {number} */
-    var countryCode = this.phoneUtil_.extractCountryCode(
-        this.nationalNumber_, numberWithoutCountryCode);
-    if (countryCode == 0) {
-      return false;
-    } else {
-      this.nationalNumber_.clear();
-      this.nationalNumber_.append(numberWithoutCountryCode.toString());
-      /** @type {string} */
-      var newRegionCode =
-          this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
-      if (newRegionCode != this.defaultCountry_) {
-        this.initializeCountrySpecificInfo_(newRegionCode);
-      }
-      /** @type {string} */
-      var countryCodeString = '' + countryCode;
-      this.prefixBeforeNationalNumber_.append(countryCodeString).append(' ');
-    }
+  if (this.nationalNumber_.getLength() == 0) {
+    return false;
+  }
+  /** @type {!goog.string.StringBuffer} */
+  var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
+  /** @type {number} */
+  var countryCode = this.phoneUtil_.extractCountryCode(
+      this.nationalNumber_, numberWithoutCountryCallingCode);
+  if (countryCode == 0) {
+    return false;
+  }
+  this.nationalNumber_.clear();
+  this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
+  /** @type {string} */
+  var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
+  if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
+      newRegionCode) {
+    this.currentMetadata_ =
+        this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
+  } else if (newRegionCode != this.defaultCountry_) {
+    this.currentMetadata_ = this.getMetadataForRegion_(newRegionCode);
+  }
+  /** @type {string} */
+  var countryCodeString = '' + countryCode;
+  this.prefixBeforeNationalNumber_.append(countryCodeString).append(
+      i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
+  // When we have successfully extracted the IDD, the previously extracted NDD
+  // should be cleared because it is no longer valid.
+  this.extractedNationalPrefix_ = '';
   return true;
 };
 
+
 /**
  * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
  * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
  * version of digits), it is first normalized to the ASCII version. The return
  * value is nextChar itself, or its normalized version, if nextChar is a digit
- * in non-ASCII format.
+ * in non-ASCII format. This method assumes its input is either a digit or the
+ * plus sign.
  *
  * @param {string} nextChar
  * @param {boolean} rememberPosition
@@ -722,43 +1061,53 @@ i18n.phonenumbers.AsYouTypeFormatter.prototype.
     normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
                                                     rememberPosition) {
 
+  /** @type {string} */
+  var normalizedChar;
   if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
+    normalizedChar = nextChar;
     this.accruedInputWithoutFormatting_.append(nextChar);
-  }
-  if (nextChar in i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS) {
-    nextChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
-    this.accruedInputWithoutFormatting_.append(nextChar);
-    this.nationalNumber_.append(nextChar);
+  } else {
+    normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
+    this.accruedInputWithoutFormatting_.append(normalizedChar);
+    this.nationalNumber_.append(normalizedChar);
   }
   if (rememberPosition) {
     this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
   }
-  return nextChar;
+  return normalizedChar;
 };
 
+
 /**
  * @param {string} nextChar
  * @return {string}
  * @private
  */
 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
-  function(nextChar) {
+    function(nextChar) {
 
+  // Note that formattingTemplate is not guaranteed to have a value, it could be
+  // empty, e.g. when the next digit is entered after extracting an IDD or NDD.
   /** @type {string} */
   var formattingTemplate = this.formattingTemplate_.toString();
   if (formattingTemplate.substring(this.lastMatchPosition_)
-      .search(this.digitPattern_) >= 0) {
+      .search(this.DIGIT_PATTERN_) >= 0) {
     /** @type {number} */
-    var digitPatternStart = formattingTemplate.search(this.digitPattern_);
+    var digitPatternStart = formattingTemplate.search(this.DIGIT_PATTERN_);
     /** @type {string} */
-    var tempTemplate = formattingTemplate.replace(this.digitPattern_, nextChar);
+    var tempTemplate =
+        formattingTemplate.replace(this.DIGIT_PATTERN_, nextChar);
     this.formattingTemplate_.clear();
     this.formattingTemplate_.append(tempTemplate);
     this.lastMatchPosition_ = digitPatternStart;
     return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
   } else {
-    // More digits are entered than we could handle.
-    this.ableToFormat_ = false;
+    if (this.possibleFormats_.length == 1) {
+      // More digits are entered than we could handle, and there are no other
+      // valid patterns to try.
+      this.ableToFormat_ = false;
+    }  // else, we just reset the formatting pattern.
+    this.currentFormattingPattern_ = '';
     return this.accruedInput_.toString();
   }
 };