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.DIGIT_PLACEHOLDER_ = '\u2008';
67 this.DIGIT_PATTERN_ = new RegExp(this.DIGIT_PLACEHOLDER_);
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;
110 * This is set to true when we know the user is entering a full national
111 * significant number, since we have either detected a national prefix or an
112 * international dialing prefix. When this is true, we will no longer use
113 * local number formatting patterns.
117 this.isCompleteNumber_ = false;
122 this.isExpectingCountryCallingCode_ = false;
124 * @type {i18n.phonenumbers.PhoneNumberUtil}
127 this.phoneUtil_ = i18n.phonenumbers.PhoneNumberUtil.getInstance();
132 this.lastMatchPosition_ = 0;
134 * The position of a digit upon which inputDigitAndRememberPosition is most
135 * recently invoked, as found in the original sequence of characters the user
140 this.originalPosition_ = 0;
142 * The position of a digit upon which inputDigitAndRememberPosition is most
143 * recently invoked, as found in accruedInputWithoutFormatting.
148 this.positionToRemember_ = 0;
150 * This contains anything that has been entered so far preceding the national
151 * significant number, and it is formatted (e.g. with space inserted). For
152 * example, this can contain IDD, country code, and/or NDD, etc.
153 * @type {!goog.string.StringBuffer}
156 this.prefixBeforeNationalNumber_ = new goog.string.StringBuffer();
161 this.shouldAddSpaceAfterNationalPrefix_ = false;
163 * This contains the national prefix that has been extracted. It contains only
164 * digits without formatting.
168 this.extractedNationalPrefix_ = '';
170 * @type {!goog.string.StringBuffer}
173 this.nationalNumber_ = new goog.string.StringBuffer();
175 * @type {Array.<i18n.phonenumbers.NumberFormat>}
178 this.possibleFormats_ = [];
183 this.defaultCountry_ = regionCode;
184 this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
186 * @type {i18n.phonenumbers.PhoneMetadata}
189 this.defaultMetadata_ = this.currentMetadata_;
194 * Character used when appropriate to separate a prefix, such as a long NDD or a
195 * country calling code, from the national number.
200 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ = ' ';
205 * @type {i18n.phonenumbers.PhoneMetadata}
208 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
209 new i18n.phonenumbers.PhoneMetadata();
210 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
211 .setInternationalPrefix('NA');
215 * A pattern that is used to match character classes in regular expressions.
216 * An example of a character class is [1-4].
221 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
226 * Any digit in a regular expression that actually denotes a digit. For
227 * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
228 * (8 and 0) are standalone digits, but the rest are not.
229 * Two look-aheads are needed because the number following \\d could be a
230 * two-digit number, since the phone number can be as long as 15 digits.
235 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
240 * A pattern that is used to determine if a numberFormat under availableFormats
241 * is eligible to be used by the AYTF. It is eligible when the format element
242 * under numberFormat contains groups of the dollar sign followed by a single
243 * digit, separated by valid phone number punctuation. This prevents invalid
244 * punctuation (such as the star sign in Israeli star numbers) getting into the
245 * output of the AYTF.
250 i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
251 '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
252 '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
256 * A set of characters that, if found in a national prefix formatting rules, are
257 * an indicator to us that we should separate the national prefix from the
258 * number when formatting.
263 i18n.phonenumbers.AsYouTypeFormatter.NATIONAL_PREFIX_SEPARATORS_PATTERN_ =
268 * This is the minimum length of national number accrued that is required to
269 * trigger the formatter. The first element of the leadingDigitsPattern of
270 * each numberFormat contains a regular expression that matches up to this
276 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
280 * The metadata needed by this class is the same for all regions sharing the
281 * same country calling code. Therefore, we return the metadata for "main"
282 * region for this country calling code.
283 * @param {string} regionCode an ISO 3166-1 two-letter region code.
284 * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
287 i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
288 function(regionCode) {
290 /** @type {number} */
291 var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
292 /** @type {string} */
294 this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
295 /** @type {i18n.phonenumbers.PhoneMetadata} */
296 var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
297 if (metadata != null) {
300 // Set to a default instance of the metadata. This allows us to function with
301 // an incorrect region code, even if formatting only works for numbers
302 // specified with '+'.
303 return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
308 * @return {boolean} true if a new template is created as opposed to reusing the
312 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
315 // When there are multiple available formats, the formatter uses the first
316 // format where a formatting template could be created.
317 /** @type {number} */
318 var possibleFormatsLength = this.possibleFormats_.length;
319 for (var i = 0; i < possibleFormatsLength; ++i) {
320 /** @type {i18n.phonenumbers.NumberFormat} */
321 var numberFormat = this.possibleFormats_[i];
322 /** @type {string} */
323 var pattern = numberFormat.getPatternOrDefault();
324 if (this.currentFormattingPattern_ == pattern) {
327 if (this.createFormattingTemplate_(numberFormat)) {
328 this.currentFormattingPattern_ = pattern;
329 this.shouldAddSpaceAfterNationalPrefix_ =
330 i18n.phonenumbers.AsYouTypeFormatter.
331 NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
332 numberFormat.getNationalPrefixFormattingRule());
333 // With a new formatting template, the matched position using the old
334 // template needs to be reset.
335 this.lastMatchPosition_ = 0;
339 this.ableToFormat_ = false;
345 * @param {string} leadingDigits leading digits of entered number.
348 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
349 function(leadingDigits) {
351 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
353 (this.isCompleteNumber_ &&
354 this.currentMetadata_.intlNumberFormatCount() > 0) ?
355 this.currentMetadata_.intlNumberFormatArray() :
356 this.currentMetadata_.numberFormatArray();
357 /** @type {number} */
358 var formatListLength = formatList.length;
359 for (var i = 0; i < formatListLength; ++i) {
360 /** @type {i18n.phonenumbers.NumberFormat} */
361 var format = formatList[i];
362 /** @type {boolean} */
363 var nationalPrefixIsUsedByCountry =
364 this.currentMetadata_.hasNationalPrefix();
365 if (!nationalPrefixIsUsedByCountry || this.isCompleteNumber_ ||
366 format.getNationalPrefixOptionalWhenFormatting() ||
367 this.phoneUtil_.formattingRuleHasFirstGroupOnly(
368 format.getNationalPrefixFormattingRuleOrDefault())) {
369 if (this.isFormatEligible_(format.getFormatOrDefault())) {
370 this.possibleFormats_.push(format);
374 this.narrowDownPossibleFormats_(leadingDigits);
379 * @param {string} format
383 i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
385 return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
391 * @param {string} leadingDigits
394 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
395 function(leadingDigits) {
397 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
398 var possibleFormats = [];
399 /** @type {number} */
400 var indexOfLeadingDigitsPattern =
401 leadingDigits.length -
402 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
403 /** @type {number} */
404 var possibleFormatsLength = this.possibleFormats_.length;
405 for (var i = 0; i < possibleFormatsLength; ++i) {
406 /** @type {i18n.phonenumbers.NumberFormat} */
407 var format = this.possibleFormats_[i];
408 if (format.leadingDigitsPatternCount() == 0) {
409 // Keep everything that isn't restricted by leading digits.
410 possibleFormats.push(this.possibleFormats_[i]);
413 /** @type {number} */
414 var lastLeadingDigitsPattern = Math.min(
415 indexOfLeadingDigitsPattern, format.leadingDigitsPatternCount() - 1);
416 /** @type {string} */
417 var leadingDigitsPattern = /** @type {string} */
418 (format.getLeadingDigitsPattern(lastLeadingDigitsPattern));
419 if (leadingDigits.search(leadingDigitsPattern) == 0) {
420 possibleFormats.push(this.possibleFormats_[i]);
423 this.possibleFormats_ = possibleFormats;
428 * @param {i18n.phonenumbers.NumberFormat} format
432 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
435 /** @type {string} */
436 var numberPattern = format.getPatternOrDefault();
438 // The formatter doesn't format numbers when numberPattern contains '|', e.g.
439 // (20|3)\d{4}. In those cases we quickly return.
440 if (numberPattern.indexOf('|') != -1) {
444 // Replace anything in the form of [..] with \d
445 numberPattern = numberPattern.replace(
446 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
448 // Replace any standalone digit (not the one in d{}) with \d
449 numberPattern = numberPattern.replace(
450 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
451 this.formattingTemplate_.clear();
452 /** @type {string} */
453 var tempTemplate = this.getFormattingTemplate_(numberPattern,
454 format.getFormatOrDefault());
455 if (tempTemplate.length > 0) {
456 this.formattingTemplate_.append(tempTemplate);
464 * Gets a formatting template which can be used to efficiently format a
465 * partial number where digits are added one by one.
467 * @param {string} numberPattern
468 * @param {string} numberFormat
472 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
473 function(numberPattern, numberFormat) {
475 // Creates a phone number consisting only of the digit 9 that matches the
476 // numberPattern by applying the pattern to the longestPhoneNumber string.
477 /** @type {string} */
478 var longestPhoneNumber = '999999999999999';
479 /** @type {Array.<string>} */
480 var m = longestPhoneNumber.match(numberPattern);
481 // this match will always succeed
482 /** @type {string} */
483 var aPhoneNumber = m[0];
484 // No formatting template can be created if the number of digits entered so
485 // far is longer than the maximum the current formatting rule can accommodate.
486 if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
489 // Formats the number according to numberFormat
490 /** @type {string} */
491 var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
493 // Replaces each digit with character DIGIT_PLACEHOLDER
494 template = template.replace(new RegExp('9', 'g'), this.DIGIT_PLACEHOLDER_);
500 * Clears the internal state of the formatter, so it can be reused.
502 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
503 this.currentOutput_ = '';
504 this.accruedInput_.clear();
505 this.accruedInputWithoutFormatting_.clear();
506 this.formattingTemplate_.clear();
507 this.lastMatchPosition_ = 0;
508 this.currentFormattingPattern_ = '';
509 this.prefixBeforeNationalNumber_.clear();
510 this.extractedNationalPrefix_ = '';
511 this.nationalNumber_.clear();
512 this.ableToFormat_ = true;
513 this.inputHasFormatting_ = false;
514 this.positionToRemember_ = 0;
515 this.originalPosition_ = 0;
516 this.isCompleteNumber_ = false;
517 this.isExpectingCountryCallingCode_ = false;
518 this.possibleFormats_ = [];
519 this.shouldAddSpaceAfterNationalPrefix_ = false;
520 if (this.currentMetadata_ != this.defaultMetadata_) {
521 this.currentMetadata_ = this.getMetadataForRegion_(this.defaultCountry_);
527 * Formats a phone number on-the-fly as each digit is entered.
529 * @param {string} nextChar the most recently entered digit of a phone number.
530 * Formatting characters are allowed, but as soon as they are encountered
531 * this method formats the number as entered and not 'as you type' anymore.
532 * Full width digits and Arabic-indic digits are allowed, and will be shown
534 * @return {string} the partially formatted phone number.
536 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
537 this.currentOutput_ =
538 this.inputDigitWithOptionToRememberPosition_(nextChar, false);
539 return this.currentOutput_;
544 * Same as {@link #inputDigit}, but remembers the position where
545 * {@code nextChar} is inserted, so that it can be retrieved later by using
546 * {@link #getRememberedPosition}. The remembered position will be automatically
547 * adjusted if additional formatting characters are later inserted/removed in
548 * front of {@code nextChar}.
550 * @param {string} nextChar
553 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
556 this.currentOutput_ =
557 this.inputDigitWithOptionToRememberPosition_(nextChar, true);
558 return this.currentOutput_;
563 * @param {string} nextChar
564 * @param {boolean} rememberPosition
568 i18n.phonenumbers.AsYouTypeFormatter.prototype.
569 inputDigitWithOptionToRememberPosition_ = function(nextChar,
572 this.accruedInput_.append(nextChar);
573 if (rememberPosition) {
574 this.originalPosition_ = this.accruedInput_.getLength();
576 // We do formatting on-the-fly only when each character entered is either a
577 // digit, or a plus sign (accepted at the start of the number only).
578 if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
579 this.ableToFormat_ = false;
580 this.inputHasFormatting_ = true;
582 nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
585 if (!this.ableToFormat_) {
586 // When we are unable to format because of reasons other than that
587 // formatting chars have been entered, it can be due to really long IDDs or
588 // NDDs. If that is the case, we might be able to do formatting again after
590 if (this.inputHasFormatting_) {
591 return this.accruedInput_.toString();
592 } else if (this.attemptToExtractIdd_()) {
593 if (this.attemptToExtractCountryCallingCode_()) {
594 return this.attemptToChoosePatternWithPrefixExtracted_();
596 } else if (this.ableToExtractLongerNdd_()) {
597 // Add an additional space to separate long NDD and national significant
598 // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
599 // to true, since we don't want this to change later when we choose
600 // formatting templates.
601 this.prefixBeforeNationalNumber_.append(
602 i18n.phonenumbers.AsYouTypeFormatter.
603 SEPARATOR_BEFORE_NATIONAL_NUMBER_);
604 return this.attemptToChoosePatternWithPrefixExtracted_();
606 return this.accruedInput_.toString();
609 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
610 // digits (the plus sign is counted as a digit as well for this purpose) have
612 switch (this.accruedInputWithoutFormatting_.getLength()) {
616 return this.accruedInput_.toString();
618 if (this.attemptToExtractIdd_()) {
619 this.isExpectingCountryCallingCode_ = true;
621 // No IDD or plus sign is found, might be entering in national format.
622 this.extractedNationalPrefix_ =
623 this.removeNationalPrefixFromNationalNumber_();
624 return this.attemptToChooseFormattingPattern_();
627 if (this.isExpectingCountryCallingCode_) {
628 if (this.attemptToExtractCountryCallingCode_()) {
629 this.isExpectingCountryCallingCode_ = false;
631 return this.prefixBeforeNationalNumber_.toString() +
632 this.nationalNumber_.toString();
634 if (this.possibleFormats_.length > 0) {
635 // The formatting patterns are already chosen.
636 /** @type {string} */
637 var tempNationalNumber = this.inputDigitHelper_(nextChar);
638 // See if the accrued digits can be formatted properly already. If not,
639 // use the results from inputDigitHelper, which does formatting based on
640 // the formatting pattern chosen.
641 /** @type {string} */
642 var formattedNumber = this.attemptToFormatAccruedDigits_();
643 if (formattedNumber.length > 0) {
644 return formattedNumber;
646 this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
647 if (this.maybeCreateNewTemplate_()) {
648 return this.inputAccruedNationalNumber_();
650 return this.ableToFormat_ ?
651 this.appendNationalNumber_(tempNationalNumber) :
652 this.accruedInput_.toString();
654 return this.attemptToChooseFormattingPattern_();
664 i18n.phonenumbers.AsYouTypeFormatter.prototype.
665 attemptToChoosePatternWithPrefixExtracted_ = function() {
667 this.ableToFormat_ = true;
668 this.isExpectingCountryCallingCode_ = false;
669 this.possibleFormats_ = [];
670 this.lastMatchPosition_ = 0;
671 this.formattingTemplate_.clear();
672 this.currentFormattingPattern_ = '';
673 return this.attemptToChooseFormattingPattern_();
681 i18n.phonenumbers.AsYouTypeFormatter.prototype.getExtractedNationalPrefix_ =
683 return this.extractedNationalPrefix_;
688 * Some national prefixes are a substring of others. If extracting the shorter
689 * NDD doesn't result in a number we can format, we try to see if we can extract
690 * a longer version here.
694 i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
696 if (this.extractedNationalPrefix_.length > 0) {
697 // Put the extracted NDD back to the national number before attempting to
698 // extract a new NDD.
699 /** @type {string} */
700 var nationalNumberStr = this.nationalNumber_.toString();
701 this.nationalNumber_.clear();
702 this.nationalNumber_.append(this.extractedNationalPrefix_);
703 this.nationalNumber_.append(nationalNumberStr);
704 // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
705 // cannot simply set it to empty string because people sometimes incorrectly
706 // enter national prefix after the country code, e.g. +44 (0)20-1234-5678.
707 /** @type {string} */
708 var prefixBeforeNationalNumberStr =
709 this.prefixBeforeNationalNumber_.toString();
710 /** @type {number} */
711 var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
712 this.extractedNationalPrefix_);
713 this.prefixBeforeNationalNumber_.clear();
714 this.prefixBeforeNationalNumber_.append(
715 prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
717 return this.extractedNationalPrefix_ !=
718 this.removeNationalPrefixFromNationalNumber_();
723 * @param {string} nextChar
727 i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
729 return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
731 (this.accruedInput_.getLength() == 1 &&
732 i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
737 * Check to see if there is an exact pattern match for these digits. If so, we
738 * should use this instead of any other formatting template whose
739 * leadingDigitsPattern also matches the input.
743 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
746 /** @type {string} */
747 var nationalNumber = this.nationalNumber_.toString();
748 /** @type {number} */
749 var possibleFormatsLength = this.possibleFormats_.length;
750 for (var i = 0; i < possibleFormatsLength; ++i) {
751 /** @type {i18n.phonenumbers.NumberFormat} */
752 var numberFormat = this.possibleFormats_[i];
753 /** @type {string} */
754 var pattern = numberFormat.getPatternOrDefault();
755 /** @type {RegExp} */
756 var patternRegExp = new RegExp('^(?:' + pattern + ')$');
757 if (patternRegExp.test(nationalNumber)) {
758 this.shouldAddSpaceAfterNationalPrefix_ =
759 i18n.phonenumbers.AsYouTypeFormatter.
760 NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
761 numberFormat.getNationalPrefixFormattingRule());
762 /** @type {string} */
763 var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
764 numberFormat.getFormat());
765 return this.appendNationalNumber_(formattedNumber);
773 * Combines the national number with any prefix (IDD/+ and country code or
774 * national prefix) that was collected. A space will be inserted between them if
775 * the current formatting template indicates this to be suitable.
776 * @param {string} nationalNumber The number to be appended.
777 * @return {string} The combined number.
780 i18n.phonenumbers.AsYouTypeFormatter.prototype.appendNationalNumber_ =
781 function(nationalNumber) {
782 /** @type {number} */
783 var prefixBeforeNationalNumberLength =
784 this.prefixBeforeNationalNumber_.getLength();
785 if (this.shouldAddSpaceAfterNationalPrefix_ &&
786 prefixBeforeNationalNumberLength > 0 &&
787 this.prefixBeforeNationalNumber_.toString().charAt(
788 prefixBeforeNationalNumberLength - 1) !=
789 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_) {
790 // We want to add a space after the national prefix if the national prefix
791 // formatting rule indicates that this would normally be done, with the
792 // exception of the case where we already appended a space because the NDD
793 // was surprisingly long.
794 return this.prefixBeforeNationalNumber_ +
795 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ +
798 return this.prefixBeforeNationalNumber_ + nationalNumber;
804 * Returns the current position in the partially formatted phone number of the
805 * character which was previously passed in as the parameter of
806 * {@link #inputDigitAndRememberPosition}.
810 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
813 if (!this.ableToFormat_) {
814 return this.originalPosition_;
816 /** @type {number} */
817 var accruedInputIndex = 0;
818 /** @type {number} */
819 var currentOutputIndex = 0;
820 /** @type {string} */
821 var accruedInputWithoutFormatting =
822 this.accruedInputWithoutFormatting_.toString();
823 /** @type {string} */
824 var currentOutput = this.currentOutput_.toString();
825 while (accruedInputIndex < this.positionToRemember_ &&
826 currentOutputIndex < currentOutput.length) {
827 if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
828 currentOutput.charAt(currentOutputIndex)) {
831 currentOutputIndex++;
833 return currentOutputIndex;
838 * Attempts to set the formatting template and returns a string which contains
839 * the formatted version of the digits entered so far.
844 i18n.phonenumbers.AsYouTypeFormatter.prototype.
845 attemptToChooseFormattingPattern_ = function() {
847 /** @type {string} */
848 var nationalNumber = this.nationalNumber_.toString();
849 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
850 // digits of national number (excluding national prefix) have been entered.
851 if (nationalNumber.length >=
852 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
853 this.getAvailableFormats_(nationalNumber);
854 // See if the accrued digits can be formatted properly already.
855 var formattedNumber = this.attemptToFormatAccruedDigits_();
856 if (formattedNumber.length > 0) {
857 return formattedNumber;
859 return this.maybeCreateNewTemplate_() ?
860 this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
862 return this.appendNationalNumber_(nationalNumber);
868 * Invokes inputDigitHelper on each digit of the national number accrued, and
869 * returns a formatted string in the end.
874 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
877 /** @type {string} */
878 var nationalNumber = this.nationalNumber_.toString();
879 /** @type {number} */
880 var lengthOfNationalNumber = nationalNumber.length;
881 if (lengthOfNationalNumber > 0) {
882 /** @type {string} */
883 var tempNationalNumber = '';
884 for (var i = 0; i < lengthOfNationalNumber; i++) {
886 this.inputDigitHelper_(nationalNumber.charAt(i));
888 return this.ableToFormat_ ?
889 this.appendNationalNumber_(tempNationalNumber) :
890 this.accruedInput_.toString();
892 return this.prefixBeforeNationalNumber_.toString();
898 * @return {boolean} true if the current country is a NANPA country and the
899 * national number begins with the national prefix.
902 i18n.phonenumbers.AsYouTypeFormatter.prototype.
903 isNanpaNumberWithNationalPrefix_ = function() {
904 // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
905 // prefix. The reason is that national significant numbers in NANPA always
906 // start with [2-9] after the national prefix. Numbers beginning with 1[01]
907 // can only be short/emergency numbers, which don't need the national prefix.
908 if (this.currentMetadata_.getCountryCode() != 1) {
911 /** @type {string} */
912 var nationalNumber = this.nationalNumber_.toString();
913 return (nationalNumber.charAt(0) == '1') &&
914 (nationalNumber.charAt(1) != '0') &&
915 (nationalNumber.charAt(1) != '1');
920 * Returns the national prefix extracted, or an empty string if it is not
925 i18n.phonenumbers.AsYouTypeFormatter.prototype.
926 removeNationalPrefixFromNationalNumber_ = function() {
928 /** @type {string} */
929 var nationalNumber = this.nationalNumber_.toString();
930 /** @type {number} */
931 var startOfNationalNumber = 0;
932 if (this.isNanpaNumberWithNationalPrefix_()) {
933 startOfNationalNumber = 1;
934 this.prefixBeforeNationalNumber_.append('1').append(
935 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
936 this.isCompleteNumber_ = true;
937 } else if (this.currentMetadata_.hasNationalPrefixForParsing()) {
938 /** @type {RegExp} */
939 var nationalPrefixForParsing = new RegExp(
940 '^(?:' + this.currentMetadata_.getNationalPrefixForParsing() + ')');
941 /** @type {Array.<string>} */
942 var m = nationalNumber.match(nationalPrefixForParsing);
943 // Since some national prefix patterns are entirely optional, check that a
944 // national prefix could actually be extracted.
945 if (m != null && m[0] != null && m[0].length > 0) {
946 // When the national prefix is detected, we use international formatting
947 // rules instead of national ones, because national formatting rules could
948 // contain local formatting rules for numbers entered without area code.
949 this.isCompleteNumber_ = true;
950 startOfNationalNumber = m[0].length;
951 this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
952 startOfNationalNumber));
955 this.nationalNumber_.clear();
956 this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
957 return nationalNumber.substring(0, startOfNationalNumber);
962 * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
963 * available, and places the remaining input into nationalNumber.
965 * @return {boolean} true when accruedInputWithoutFormatting begins with the
966 * plus sign or valid IDD for defaultCountry.
969 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
972 /** @type {string} */
973 var accruedInputWithoutFormatting =
974 this.accruedInputWithoutFormatting_.toString();
975 /** @type {RegExp} */
976 var internationalPrefix = new RegExp(
977 '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
978 this.currentMetadata_.getInternationalPrefix() + ')');
979 /** @type {Array.<string>} */
980 var m = accruedInputWithoutFormatting.match(internationalPrefix);
981 if (m != null && m[0] != null && m[0].length > 0) {
982 this.isCompleteNumber_ = true;
983 /** @type {number} */
984 var startOfCountryCallingCode = m[0].length;
985 this.nationalNumber_.clear();
986 this.nationalNumber_.append(
987 accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
988 this.prefixBeforeNationalNumber_.clear();
989 this.prefixBeforeNationalNumber_.append(
990 accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
991 if (accruedInputWithoutFormatting.charAt(0) !=
992 i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
993 this.prefixBeforeNationalNumber_.append(
994 i18n.phonenumbers.AsYouTypeFormatter.
995 SEPARATOR_BEFORE_NATIONAL_NUMBER_);
1004 * Extracts the country calling code from the beginning of nationalNumber to
1005 * prefixBeforeNationalNumber when they are available, and places the remaining
1006 * input into nationalNumber.
1008 * @return {boolean} true when a valid country calling code can be found.
1011 i18n.phonenumbers.AsYouTypeFormatter.prototype.
1012 attemptToExtractCountryCallingCode_ = function() {
1014 if (this.nationalNumber_.getLength() == 0) {
1017 /** @type {!goog.string.StringBuffer} */
1018 var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
1019 /** @type {number} */
1020 var countryCode = this.phoneUtil_.extractCountryCode(
1021 this.nationalNumber_, numberWithoutCountryCallingCode);
1022 if (countryCode == 0) {
1025 this.nationalNumber_.clear();
1026 this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
1027 /** @type {string} */
1028 var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
1029 if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
1031 this.currentMetadata_ =
1032 this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
1033 } else if (newRegionCode != this.defaultCountry_) {
1034 this.currentMetadata_ = this.getMetadataForRegion_(newRegionCode);
1036 /** @type {string} */
1037 var countryCodeString = '' + countryCode;
1038 this.prefixBeforeNationalNumber_.append(countryCodeString).append(
1039 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
1040 // When we have successfully extracted the IDD, the previously extracted NDD
1041 // should be cleared because it is no longer valid.
1042 this.extractedNationalPrefix_ = '';
1048 * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
1049 * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
1050 * version of digits), it is first normalized to the ASCII version. The return
1051 * value is nextChar itself, or its normalized version, if nextChar is a digit
1052 * in non-ASCII format. This method assumes its input is either a digit or the
1055 * @param {string} nextChar
1056 * @param {boolean} rememberPosition
1060 i18n.phonenumbers.AsYouTypeFormatter.prototype.
1061 normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
1064 /** @type {string} */
1066 if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
1067 normalizedChar = nextChar;
1068 this.accruedInputWithoutFormatting_.append(nextChar);
1070 normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
1071 this.accruedInputWithoutFormatting_.append(normalizedChar);
1072 this.nationalNumber_.append(normalizedChar);
1074 if (rememberPosition) {
1075 this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
1077 return normalizedChar;
1082 * @param {string} nextChar
1086 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
1087 function(nextChar) {
1089 // Note that formattingTemplate is not guaranteed to have a value, it could be
1090 // empty, e.g. when the next digit is entered after extracting an IDD or NDD.
1091 /** @type {string} */
1092 var formattingTemplate = this.formattingTemplate_.toString();
1093 if (formattingTemplate.substring(this.lastMatchPosition_)
1094 .search(this.DIGIT_PATTERN_) >= 0) {
1095 /** @type {number} */
1096 var digitPatternStart = formattingTemplate.search(this.DIGIT_PATTERN_);
1097 /** @type {string} */
1099 formattingTemplate.replace(this.DIGIT_PATTERN_, nextChar);
1100 this.formattingTemplate_.clear();
1101 this.formattingTemplate_.append(tempTemplate);
1102 this.lastMatchPosition_ = digitPatternStart;
1103 return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
1105 if (this.possibleFormats_.length == 1) {
1106 // More digits are entered than we could handle, and there are no other
1107 // valid patterns to try.
1108 this.ableToFormat_ = false;
1109 } // else, we just reset the formatting pattern.
1110 this.currentFormattingPattern_ = '';
1111 return this.accruedInput_.toString();