3 * Copyright (C) 2010 The Libphonenumber Authors
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 * @fileoverview A formatter which formats phone numbers as they are entered.
20 * (based on the java implementation).
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
28 * <p>See the unittests for more details on how the formatter is to be used.
30 * @author Nikolaos Trogkanis
33 goog.provide('i18n.phonenumbers.AsYouTypeFormatter');
35 goog.require('goog.string.StringBuffer');
36 goog.require('i18n.phonenumbers.NumberFormat');
37 goog.require('i18n.phonenumbers.PhoneMetadata');
38 goog.require('i18n.phonenumbers.PhoneMetadataCollection');
39 goog.require('i18n.phonenumbers.PhoneNumber');
40 goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource');
41 goog.require('i18n.phonenumbers.PhoneNumberDesc');
42 goog.require('i18n.phonenumbers.PhoneNumberUtil');
43 goog.require('i18n.phonenumbers.metadata');
48 * Constructs an AsYouTypeFormatter for the specific region.
50 * @param {string} regionCode the ISO 3166-1 two-letter region code that denotes
51 * the region where the phone number is being entered.
54 i18n.phonenumbers.AsYouTypeFormatter = function(regionCode) {
56 * The digits that have not been entered yet will be represented by a \u2008,
57 * the punctuation space.
62 this.digitPlaceholder_ = '\u2008';
67 this.digitPattern_ = new RegExp(this.digitPlaceholder_);
72 this.currentOutput_ = '';
74 * @type {!goog.string.StringBuffer}
77 this.formattingTemplate_ = new goog.string.StringBuffer();
79 * The pattern from numberFormat that is currently used to create
84 this.currentFormattingPattern_ = '';
86 * @type {!goog.string.StringBuffer}
89 this.accruedInput_ = new goog.string.StringBuffer();
91 * @type {!goog.string.StringBuffer}
94 this.accruedInputWithoutFormatting_ = new goog.string.StringBuffer();
96 * This indicates whether AsYouTypeFormatter is currently doing the
101 this.ableToFormat_ = true;
103 * Set to true when users enter their own formatting. AsYouTypeFormatter will
104 * do no formatting at all when this is set to true.
108 this.inputHasFormatting_ = false;
113 this.isInternationalFormatting_ = false;
118 this.isExpectingCountryCallingCode_ = false;
120 * @type {i18n.phonenumbers.PhoneNumberUtil}
123 this.phoneUtil_ = i18n.phonenumbers.PhoneNumberUtil.getInstance();
128 this.lastMatchPosition_ = 0;
130 * The position of a digit upon which inputDigitAndRememberPosition is most
131 * recently invoked, as found in the original sequence of characters the user
136 this.originalPosition_ = 0;
138 * The position of a digit upon which inputDigitAndRememberPosition is most
139 * recently invoked, as found in accruedInputWithoutFormatting.
144 this.positionToRemember_ = 0;
146 * This contains anything that has been entered so far preceding the national
147 * significant number, and it is formatted (e.g. with space inserted). For
148 * example, this can contain IDD, country code, and/or NDD, etc.
149 * @type {!goog.string.StringBuffer}
152 this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
154 * This contains the national prefix that has been extracted. It contains only
155 * digits without formatting.
159 this.nationalPrefixExtracted_ = '';
161 * @type {!goog.string.StringBuffer}
164 this.nationalNumber_ = new goog.string.StringBuffer();
166 * @type {Array.<i18n.phonenumbers.NumberFormat>}
169 this.possibleFormats_ = [];
174 this.defaultCountry_ = regionCode;
175 this.currentMetaData_ = this.getMetadataForRegion_(this.defaultCountry_);
177 * @type {i18n.phonenumbers.PhoneMetadata}
180 this.defaultMetaData_ = this.currentMetaData_;
186 * @type {i18n.phonenumbers.PhoneMetadata}
189 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
190 new i18n.phonenumbers.PhoneMetadata();
191 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
192 .setInternationalPrefix('NA');
196 * A pattern that is used to match character classes in regular expressions.
197 * An example of a character class is [1-4].
202 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
207 * Any digit in a regular expression that actually denotes a digit. For
208 * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
209 * (8 and 0) are standalone digits, but the rest are not.
210 * Two look-aheads are needed because the number following \\d could be a
211 * two-digit number, since the phone number can be as long as 15 digits.
216 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
221 * A pattern that is used to determine if a numberFormat under availableFormats
222 * is eligible to be used by the AYTF. It is eligible when the format element
223 * under numberFormat contains groups of the dollar sign followed by a single
224 * digit, separated by valid phone number punctuation. This prevents invalid
225 * punctuation (such as the star sign in Israeli star numbers) getting into the
226 * output of the AYTF.
231 i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
232 '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
233 '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
237 * This is the minimum length of national number accrued that is required to
238 * trigger the formatter. The first element of the leadingDigitsPattern of
239 * each numberFormat contains a regular expression that matches up to this
245 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
249 * The metadata needed by this class is the same for all regions sharing the
250 * same country calling code. Therefore, we return the metadata for "main"
251 * region for this country calling code.
252 * @param {string} regionCode
253 * @return {i18n.phonenumbers.PhoneMetadata}
256 i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
257 function(regionCode) {
259 /** @type {number} */
260 var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
261 /** @type {string} */
263 this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
264 /** @type {i18n.phonenumbers.PhoneMetadata} */
265 var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
266 if (metadata != null) {
269 // Set to a default instance of the metadata. This allows us to function with
270 // an incorrect region code, even if formatting only works for numbers
271 // specified with '+'.
272 return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
277 * @return {boolean} true if a new template is created as opposed to reusing the
281 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
284 // When there are multiple available formats, the formatter uses the first
285 // format where a formatting template could be created.
286 /** @type {number} */
287 var possibleFormatsLength = this.possibleFormats_.length;
288 for (var i = 0; i < possibleFormatsLength; ++i) {
289 /** @type {i18n.phonenumbers.NumberFormat} */
290 var numberFormat = this.possibleFormats_[i];
291 /** @type {string} */
292 var pattern = numberFormat.getPatternOrDefault();
293 if (this.currentFormattingPattern_ == pattern) {
296 if (this.createFormattingTemplate_(numberFormat)) {
297 this.currentFormattingPattern_ = pattern;
298 // With a new formatting template, the matched position using the old
299 // template needs to be reset.
300 this.lastMatchPosition_ = 0;
304 this.ableToFormat_ = false;
310 * @param {string} leadingThreeDigits
313 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
314 function(leadingThreeDigits) {
316 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
318 (this.isInternationalFormatting_ &&
319 this.currentMetaData_.intlNumberFormatCount() > 0) ?
320 this.currentMetaData_.intlNumberFormatArray() :
321 this.currentMetaData_.numberFormatArray();
322 /** @type {number} */
323 var formatListLength = formatList.length;
324 for (var i = 0; i < formatListLength; ++i) {
325 /** @type {i18n.phonenumbers.NumberFormat} */
326 var format = formatList[i];
327 if (this.isFormatEligible_(format.getFormatOrDefault())) {
328 this.possibleFormats_.push(format);
331 this.narrowDownPossibleFormats_(leadingThreeDigits);
336 * @param {string} format
340 i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
342 return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
348 * @param {string} leadingDigits
351 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
352 function(leadingDigits) {
354 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
355 var possibleFormats = [];
356 /** @type {number} */
357 var indexOfLeadingDigitsPattern =
358 leadingDigits.length -
359 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
360 /** @type {number} */
361 var possibleFormatsLength = this.possibleFormats_.length;
362 for (var i = 0; i < possibleFormatsLength; ++i) {
363 /** @type {i18n.phonenumbers.NumberFormat} */
364 var format = this.possibleFormats_[i];
365 if (format.leadingDigitsPatternCount() > indexOfLeadingDigitsPattern) {
366 /** @type {string} */
367 var leadingDigitsPattern =
368 format.getLeadingDigitsPatternOrDefault(indexOfLeadingDigitsPattern);
369 if (leadingDigits.search(leadingDigitsPattern) == 0) {
370 possibleFormats.push(this.possibleFormats_[i]);
373 // else the particular format has no more specific leadingDigitsPattern,
374 // and it should be retained.
375 possibleFormats.push(this.possibleFormats_[i]);
378 this.possibleFormats_ = possibleFormats;
383 * @param {i18n.phonenumbers.NumberFormat} format
387 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
390 /** @type {string} */
391 var numberPattern = format.getPatternOrDefault();
393 // The formatter doesn't format numbers when numberPattern contains '|', e.g.
394 // (20|3)\d{4}. In those cases we quickly return.
395 if (numberPattern.indexOf('|') != -1) {
399 // Replace anything in the form of [..] with \d
400 numberPattern = numberPattern.replace(
401 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
403 // Replace any standalone digit (not the one in d{}) with \d
404 numberPattern = numberPattern.replace(
405 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
406 this.formattingTemplate_.clear();
407 /** @type {string} */
408 var tempTemplate = this.getFormattingTemplate_(numberPattern,
409 format.getFormatOrDefault());
410 if (tempTemplate.length > 0) {
411 this.formattingTemplate_.append(tempTemplate);
419 * Gets a formatting template which can be used to efficiently format a
420 * partial number where digits are added one by one.
422 * @param {string} numberPattern
423 * @param {string} numberFormat
427 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
428 function(numberPattern, numberFormat) {
430 // Creates a phone number consisting only of the digit 9 that matches the
431 // numberPattern by applying the pattern to the longestPhoneNumber string.
432 /** @type {string} */
433 var longestPhoneNumber = '999999999999999';
434 /** @type {Array.<string>} */
435 var m = longestPhoneNumber.match(numberPattern);
436 // this match will always succeed
437 /** @type {string} */
438 var aPhoneNumber = m[0];
439 // No formatting template can be created if the number of digits entered so
440 // far is longer than the maximum the current formatting rule can accommodate.
441 if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
444 // Formats the number according to numberFormat
445 /** @type {string} */
446 var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
448 // Replaces each digit with character digitPlaceholder
449 template = template.replace(new RegExp('9', 'g'), this.digitPlaceholder_);
455 * Clears the internal state of the formatter, so it can be reused.
457 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
458 this.currentOutput_ = '';
459 this.accruedInput_.clear();
460 this.accruedInputWithoutFormatting_.clear();
461 this.formattingTemplate_.clear();
462 this.lastMatchPosition_ = 0;
463 this.currentFormattingPattern_ = '';
464 this.prefixBeforeNationalNumber_.clear();
465 this.nationalPrefixExtracted_ = '';
466 this.nationalNumber_.clear();
467 this.ableToFormat_ = true;
468 this.inputHasFormatting_ = false;
469 this.positionToRemember_ = 0;
470 this.originalPosition_ = 0;
471 this.isInternationalFormatting_ = false;
472 this.isExpectingCountryCallingCode_ = false;
473 this.possibleFormats_ = [];
474 if (this.currentMetaData_ != this.defaultMetaData_) {
475 this.currentMetaData_ = this.getMetadataForRegion_(this.defaultCountry_);
481 * Formats a phone number on-the-fly as each digit is entered.
483 * @param {string} nextChar the most recently entered digit of a phone number.
484 * Formatting characters are allowed, but as soon as they are encountered
485 * this method formats the number as entered and not 'as you type' anymore.
486 * Full width digits and Arabic-indic digits are allowed, and will be shown
488 * @return {string} the partially formatted phone number.
490 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
491 this.currentOutput_ =
492 this.inputDigitWithOptionToRememberPosition_(nextChar, false);
493 return this.currentOutput_;
498 * Same as {@link #inputDigit}, but remembers the position where
499 * {@code nextChar} is inserted, so that it can be retrieved later by using
500 * {@link #getRememberedPosition}. The remembered position will be automatically
501 * adjusted if additional formatting characters are later inserted/removed in
502 * front of {@code nextChar}.
504 * @param {string} nextChar
507 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
510 this.currentOutput_ =
511 this.inputDigitWithOptionToRememberPosition_(nextChar, true);
512 return this.currentOutput_;
517 * @param {string} nextChar
518 * @param {boolean} rememberPosition
522 i18n.phonenumbers.AsYouTypeFormatter.prototype.
523 inputDigitWithOptionToRememberPosition_ = function(nextChar,
526 this.accruedInput_.append(nextChar);
527 if (rememberPosition) {
528 this.originalPosition_ = this.accruedInput_.getLength();
530 // We do formatting on-the-fly only when each character entered is either a
531 // digit, or a plus sign (accepted at the start of the number only).
532 if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
533 this.ableToFormat_ = false;
534 this.inputHasFormatting_ = true;
536 nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
539 if (!this.ableToFormat_) {
540 // When we are unable to format because of reasons other than that
541 // formatting chars have been entered, it can be due to really long IDDs or
542 // NDDs. If that is the case, we might be able to do formatting again after
544 if (this.inputHasFormatting_) {
545 return this.accruedInput_.toString();
546 } else if (this.attemptToExtractIdd_()) {
547 if (this.attemptToExtractCountryCallingCode_()) {
548 return this.attemptToChoosePatternWithPrefixExtracted_();
550 } else if (this.ableToExtractLongerNdd_()) {
551 // Add an additional space to separate long NDD and national significant
552 // number for readability.
553 this.prefixBeforeNationalNumber_.append(' ');
554 return this.attemptToChoosePatternWithPrefixExtracted_();
556 return this.accruedInput_.toString();
559 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
560 // digits (the plus sign is counted as a digit as well for this purpose) have
562 switch (this.accruedInputWithoutFormatting_.getLength()) {
566 return this.accruedInput_.toString();
568 if (this.attemptToExtractIdd_()) {
569 this.isExpectingCountryCallingCode_ = true;
571 // No IDD or plus sign is found, might be entering in national format.
572 this.nationalPrefixExtracted_ =
573 this.removeNationalPrefixFromNationalNumber_();
574 return this.attemptToChooseFormattingPattern_();
577 if (this.isExpectingCountryCallingCode_) {
578 if (this.attemptToExtractCountryCallingCode_()) {
579 this.isExpectingCountryCallingCode_ = false;
581 return this.prefixBeforeNationalNumber_.toString() +
582 this.nationalNumber_.toString();
584 if (this.possibleFormats_.length > 0) {
585 // The formatting pattern is already chosen.
586 /** @type {string} */
587 var tempNationalNumber = this.inputDigitHelper_(nextChar);
588 // See if the accrued digits can be formatted properly already. If not,
589 // use the results from inputDigitHelper, which does formatting based on
590 // the formatting pattern chosen.
591 /** @type {string} */
592 var formattedNumber = this.attemptToFormatAccruedDigits_();
593 if (formattedNumber.length > 0) {
594 return formattedNumber;
596 this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
597 if (this.maybeCreateNewTemplate_()) {
598 return this.inputAccruedNationalNumber_();
600 return this.ableToFormat_ ?
601 this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
602 this.accruedInput_.toString();
604 return this.attemptToChooseFormattingPattern_();
614 i18n.phonenumbers.AsYouTypeFormatter.prototype.
615 attemptToChoosePatternWithPrefixExtracted_ = function() {
617 this.ableToFormat_ = true;
618 this.isExpectingCountryCallingCode_ = false;
619 this.possibleFormats_ = [];
620 return this.attemptToChooseFormattingPattern_();
625 * Some national prefixes are a substring of others. If extracting the shorter
626 * NDD doesn't result in a number we can format, we try to see if we can extract
627 * a longer version here.
631 i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
633 if (this.nationalPrefixExtracted_.length > 0) {
634 // Put the extracted NDD back to the national number before attempting to
635 // extract a new NDD.
636 /** @type {string} */
637 var nationalNumberStr = this.nationalNumber_.toString();
638 this.nationalNumber_.clear();
639 this.nationalNumber_.append(this.nationalPrefixExtracted_);
640 this.nationalNumber_.append(nationalNumberStr);
641 // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
642 // cannot simply set it to empty string because people sometimes enter
643 // national prefix after country code, e.g +44 (0)20-1234-5678.
644 /** @type {string} */
645 var prefixBeforeNationalNumberStr =
646 this.prefixBeforeNationalNumber_.toString();
647 /** @type {number} */
648 var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
649 this.nationalPrefixExtracted_);
650 this.prefixBeforeNationalNumber_.clear();
651 this.prefixBeforeNationalNumber_.append(
652 prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
654 return this.nationalPrefixExtracted_ !=
655 this.removeNationalPrefixFromNationalNumber_();
660 * @param {string} nextChar
664 i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
666 return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
668 (this.accruedInput_.getLength() == 1 &&
669 i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
677 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
680 /** @type {string} */
681 var nationalNumber = this.nationalNumber_.toString();
682 /** @type {number} */
683 var possibleFormatsLength = this.possibleFormats_.length;
684 for (var i = 0; i < possibleFormatsLength; ++i) {
685 /** @type {i18n.phonenumbers.NumberFormat} */
686 var numFormat = this.possibleFormats_[i];
687 /** @type {string} */
688 var pattern = numFormat.getPatternOrDefault();
689 /** @type {RegExp} */
690 var patternRegExp = new RegExp('^(?:' + pattern + ')$');
691 if (patternRegExp.test(nationalNumber)) {
692 /** @type {string} */
693 var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
694 numFormat.getFormat());
695 return this.prefixBeforeNationalNumber_.toString() + formattedNumber;
703 * Returns the current position in the partially formatted phone number of the
704 * character which was previously passed in as the parameter of
705 * {@link #inputDigitAndRememberPosition}.
709 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
712 if (!this.ableToFormat_) {
713 return this.originalPosition_;
715 /** @type {number} */
716 var accruedInputIndex = 0;
717 /** @type {number} */
718 var currentOutputIndex = 0;
719 /** @type {string} */
720 var accruedInputWithoutFormatting =
721 this.accruedInputWithoutFormatting_.toString();
722 /** @type {string} */
723 var currentOutput = this.currentOutput_.toString();
724 while (accruedInputIndex < this.positionToRemember_ &&
725 currentOutputIndex < currentOutput.length) {
726 if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
727 currentOutput.charAt(currentOutputIndex)) {
730 currentOutputIndex++;
732 return currentOutputIndex;
737 * Attempts to set the formatting template and returns a string which contains
738 * the formatted version of the digits entered so far.
743 i18n.phonenumbers.AsYouTypeFormatter.prototype.
744 attemptToChooseFormattingPattern_ = function() {
746 /** @type {string} */
747 var nationalNumber = this.nationalNumber_.toString();
748 // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
749 // digits of national number (excluding national prefix) have been entered.
750 if (nationalNumber.length >=
751 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
752 this.getAvailableFormats_(
753 nationalNumber.substring(0,
754 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_));
755 return this.maybeCreateNewTemplate_() ?
756 this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
758 return this.prefixBeforeNationalNumber_.toString() + nationalNumber;
764 * Invokes inputDigitHelper on each digit of the national number accrued, and
765 * returns a formatted string in the end.
770 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
773 /** @type {string} */
774 var nationalNumber = this.nationalNumber_.toString();
775 /** @type {number} */
776 var lengthOfNationalNumber = nationalNumber.length;
777 if (lengthOfNationalNumber > 0) {
778 /** @type {string} */
779 var tempNationalNumber = '';
780 for (var i = 0; i < lengthOfNationalNumber; i++) {
782 this.inputDigitHelper_(nationalNumber.charAt(i));
784 return this.ableToFormat_ ?
785 this.prefixBeforeNationalNumber_.toString() + tempNationalNumber :
786 this.accruedInput_.toString();
788 return this.prefixBeforeNationalNumber_.toString();
794 * Returns the national prefix extracted, or an empty string if it is not
799 i18n.phonenumbers.AsYouTypeFormatter.prototype.
800 removeNationalPrefixFromNationalNumber_ = function() {
802 /** @type {string} */
803 var nationalNumber = this.nationalNumber_.toString();
804 /** @type {number} */
805 var startOfNationalNumber = 0;
806 if (this.currentMetaData_.getCountryCode() == 1 &&
807 nationalNumber.charAt(0) == '1') {
808 startOfNationalNumber = 1;
809 this.prefixBeforeNationalNumber_.append('1 ');
810 this.isInternationalFormatting_ = true;
811 } else if (this.currentMetaData_.hasNationalPrefixForParsing()) {
812 /** @type {RegExp} */
813 var nationalPrefixForParsing = new RegExp(
814 '^(?:' + this.currentMetaData_.getNationalPrefixForParsing() + ')');
815 /** @type {Array.<string>} */
816 var m = nationalNumber.match(nationalPrefixForParsing);
817 if (m != null && m[0] != null && m[0].length > 0) {
818 // When the national prefix is detected, we use international formatting
819 // rules instead of national ones, because national formatting rules could
820 // contain local formatting rules for numbers entered without area code.
821 this.isInternationalFormatting_ = true;
822 startOfNationalNumber = m[0].length;
823 this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
824 startOfNationalNumber));
827 this.nationalNumber_.clear();
828 this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
829 return nationalNumber.substring(0, startOfNationalNumber);
834 * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
835 * available, and places the remaining input into nationalNumber.
837 * @return {boolean} true when accruedInputWithoutFormatting begins with the
838 * plus sign or valid IDD for defaultCountry.
841 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
844 /** @type {string} */
845 var accruedInputWithoutFormatting =
846 this.accruedInputWithoutFormatting_.toString();
847 /** @type {RegExp} */
848 var internationalPrefix = new RegExp(
849 '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
850 this.currentMetaData_.getInternationalPrefix() + ')');
851 /** @type {Array.<string>} */
852 var m = accruedInputWithoutFormatting.match(internationalPrefix);
853 if (m != null && m[0] != null && m[0].length > 0) {
854 this.isInternationalFormatting_ = true;
855 /** @type {number} */
856 var startOfCountryCallingCode = m[0].length;
857 this.nationalNumber_.clear();
858 this.nationalNumber_.append(
859 accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
860 this.prefixBeforeNationalNumber_.clear();
861 this.prefixBeforeNationalNumber_.append(
862 accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
863 if (accruedInputWithoutFormatting.charAt(0) !=
864 i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
865 this.prefixBeforeNationalNumber_.append(' ');
874 * Extracts the country calling code from the beginning of nationalNumber to
875 * prefixBeforeNationalNumber when they are available, and places the remaining
876 * input into nationalNumber.
878 * @return {boolean} true when a valid country calling code can be found.
881 i18n.phonenumbers.AsYouTypeFormatter.prototype.
882 attemptToExtractCountryCallingCode_ = function() {
884 if (this.nationalNumber_.getLength() == 0) {
887 /** @type {!goog.string.StringBuffer} */
888 var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
889 /** @type {number} */
890 var countryCode = this.phoneUtil_.extractCountryCode(
891 this.nationalNumber_, numberWithoutCountryCallingCode);
892 if (countryCode == 0) {
895 this.nationalNumber_.clear();
896 this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
897 /** @type {string} */
898 var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
899 if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
901 this.currentMetaData_ =
902 this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
903 } else if (newRegionCode != this.defaultCountry_) {
904 this.currentMetaData_ = this.getMetadataForRegion_(newRegionCode);
906 /** @type {string} */
907 var countryCodeString = '' + countryCode;
908 this.prefixBeforeNationalNumber_.append(countryCodeString).append(' ');
914 * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
915 * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
916 * version of digits), it is first normalized to the ASCII version. The return
917 * value is nextChar itself, or its normalized version, if nextChar is a digit
918 * in non-ASCII format. This method assumes its input is either a digit or the
921 * @param {string} nextChar
922 * @param {boolean} rememberPosition
926 i18n.phonenumbers.AsYouTypeFormatter.prototype.
927 normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
930 /** @type {string} */
932 if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
933 normalizedChar = nextChar;
934 this.accruedInputWithoutFormatting_.append(nextChar);
936 normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
937 this.accruedInputWithoutFormatting_.append(normalizedChar);
938 this.nationalNumber_.append(normalizedChar);
940 if (rememberPosition) {
941 this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
943 return normalizedChar;
948 * @param {string} nextChar
952 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
955 /** @type {string} */
956 var formattingTemplate = this.formattingTemplate_.toString();
957 if (formattingTemplate.substring(this.lastMatchPosition_)
958 .search(this.digitPattern_) >= 0) {
959 /** @type {number} */
960 var digitPatternStart = formattingTemplate.search(this.digitPattern_);
961 /** @type {string} */
962 var tempTemplate = formattingTemplate.replace(this.digitPattern_, nextChar);
963 this.formattingTemplate_.clear();
964 this.formattingTemplate_.append(tempTemplate);
965 this.lastMatchPosition_ = digitPatternStart;
966 return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
968 if (this.possibleFormats_.length == 1) {
969 // More digits are entered than we could handle, and there are no other
970 // valid patterns to try.
971 this.ableToFormat_ = false;
972 } // else, we just reset the formatting pattern.
973 this.currentFormattingPattern_ = '';
974 return this.accruedInput_.toString();