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.nationalPrefixExtracted_ = '';
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_ = ' ';
204 * @type {i18n.phonenumbers.PhoneMetadata}
207 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_ =
208 new i18n.phonenumbers.PhoneMetadata();
209 i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_
210 .setInternationalPrefix('NA');
214 * A pattern that is used to match character classes in regular expressions.
215 * An example of a character class is [1-4].
220 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_ =
225 * Any digit in a regular expression that actually denotes a digit. For
226 * example, in the regular expression 80[0-2]\d{6,10}, the first 2 digits
227 * (8 and 0) are standalone digits, but the rest are not.
228 * Two look-aheads are needed because the number following \\d could be a
229 * two-digit number, since the phone number can be as long as 15 digits.
234 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_ =
239 * A pattern that is used to determine if a numberFormat under availableFormats
240 * is eligible to be used by the AYTF. It is eligible when the format element
241 * under numberFormat contains groups of the dollar sign followed by a single
242 * digit, separated by valid phone number punctuation. This prevents invalid
243 * punctuation (such as the star sign in Israeli star numbers) getting into the
244 * output of the AYTF.
249 i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_ = new RegExp(
250 '^[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*' +
251 '(\\$\\d[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']*)+$');
255 * A set of characters that, if found in a national prefix formatting rules, are
256 * an indicator to us that we should separate the national prefix from the
257 * number when formatting.
262 i18n.phonenumbers.AsYouTypeFormatter.NATIONAL_PREFIX_SEPARATORS_PATTERN_ =
267 * This is the minimum length of national number accrued that is required to
268 * trigger the formatter. The first element of the leadingDigitsPattern of
269 * each numberFormat contains a regular expression that matches up to this
275 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_ = 3;
279 * The metadata needed by this class is the same for all regions sharing the
280 * same country calling code. Therefore, we return the metadata for "main"
281 * region for this country calling code.
282 * @param {string} regionCode an ISO 3166-1 two-letter region code.
283 * @return {i18n.phonenumbers.PhoneMetadata} main metadata for this region.
286 i18n.phonenumbers.AsYouTypeFormatter.prototype.getMetadataForRegion_ =
287 function(regionCode) {
289 /** @type {number} */
290 var countryCallingCode = this.phoneUtil_.getCountryCodeForRegion(regionCode);
291 /** @type {string} */
293 this.phoneUtil_.getRegionCodeForCountryCode(countryCallingCode);
294 /** @type {i18n.phonenumbers.PhoneMetadata} */
295 var metadata = this.phoneUtil_.getMetadataForRegion(mainCountry);
296 if (metadata != null) {
299 // Set to a default instance of the metadata. This allows us to function with
300 // an incorrect region code, even if formatting only works for numbers
301 // specified with '+'.
302 return i18n.phonenumbers.AsYouTypeFormatter.EMPTY_METADATA_;
307 * @return {boolean} true if a new template is created as opposed to reusing the
311 i18n.phonenumbers.AsYouTypeFormatter.prototype.maybeCreateNewTemplate_ =
314 // When there are multiple available formats, the formatter uses the first
315 // format where a formatting template could be created.
316 /** @type {number} */
317 var possibleFormatsLength = this.possibleFormats_.length;
318 for (var i = 0; i < possibleFormatsLength; ++i) {
319 /** @type {i18n.phonenumbers.NumberFormat} */
320 var numberFormat = this.possibleFormats_[i];
321 /** @type {string} */
322 var pattern = numberFormat.getPatternOrDefault();
323 if (this.currentFormattingPattern_ == pattern) {
326 if (this.createFormattingTemplate_(numberFormat)) {
327 this.currentFormattingPattern_ = pattern;
328 this.shouldAddSpaceAfterNationalPrefix_ =
329 i18n.phonenumbers.AsYouTypeFormatter.
330 NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
331 numberFormat.getNationalPrefixFormattingRule());
332 // With a new formatting template, the matched position using the old
333 // template needs to be reset.
334 this.lastMatchPosition_ = 0;
338 this.ableToFormat_ = false;
344 * @param {string} leadingThreeDigits first three digits of entered number.
347 i18n.phonenumbers.AsYouTypeFormatter.prototype.getAvailableFormats_ =
348 function(leadingThreeDigits) {
350 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
352 (this.isCompleteNumber_ &&
353 this.currentMetaData_.intlNumberFormatCount() > 0) ?
354 this.currentMetaData_.intlNumberFormatArray() :
355 this.currentMetaData_.numberFormatArray();
356 /** @type {number} */
357 var formatListLength = formatList.length;
358 for (var i = 0; i < formatListLength; ++i) {
359 /** @type {i18n.phonenumbers.NumberFormat} */
360 var format = formatList[i];
361 if (this.isCompleteNumber_ ||
362 format.getNationalPrefixOptionalWhenFormatting() ||
363 this.phoneUtil_.formattingRuleHasFirstGroupOnly(
364 format.getNationalPrefixFormattingRule())) {
365 if (this.isFormatEligible_(format.getFormatOrDefault())) {
366 this.possibleFormats_.push(format);
370 this.narrowDownPossibleFormats_(leadingThreeDigits);
375 * @param {string} format
379 i18n.phonenumbers.AsYouTypeFormatter.prototype.isFormatEligible_ =
381 return i18n.phonenumbers.AsYouTypeFormatter.ELIGIBLE_FORMAT_PATTERN_
387 * @param {string} leadingDigits
390 i18n.phonenumbers.AsYouTypeFormatter.prototype.narrowDownPossibleFormats_ =
391 function(leadingDigits) {
393 /** @type {Array.<i18n.phonenumbers.NumberFormat>} */
394 var possibleFormats = [];
395 /** @type {number} */
396 var indexOfLeadingDigitsPattern =
397 leadingDigits.length -
398 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_;
399 /** @type {number} */
400 var possibleFormatsLength = this.possibleFormats_.length;
401 for (var i = 0; i < possibleFormatsLength; ++i) {
402 /** @type {i18n.phonenumbers.NumberFormat} */
403 var format = this.possibleFormats_[i];
404 if (format.leadingDigitsPatternCount() > indexOfLeadingDigitsPattern) {
405 /** @type {string} */
406 var leadingDigitsPattern =
407 format.getLeadingDigitsPatternOrDefault(indexOfLeadingDigitsPattern);
408 if (leadingDigits.search(leadingDigitsPattern) == 0) {
409 possibleFormats.push(this.possibleFormats_[i]);
412 // else the particular format has no more specific leadingDigitsPattern,
413 // and it should be retained.
414 possibleFormats.push(this.possibleFormats_[i]);
417 this.possibleFormats_ = possibleFormats;
422 * @param {i18n.phonenumbers.NumberFormat} format
426 i18n.phonenumbers.AsYouTypeFormatter.prototype.createFormattingTemplate_ =
429 /** @type {string} */
430 var numberPattern = format.getPatternOrDefault();
432 // The formatter doesn't format numbers when numberPattern contains '|', e.g.
433 // (20|3)\d{4}. In those cases we quickly return.
434 if (numberPattern.indexOf('|') != -1) {
438 // Replace anything in the form of [..] with \d
439 numberPattern = numberPattern.replace(
440 i18n.phonenumbers.AsYouTypeFormatter.CHARACTER_CLASS_PATTERN_, '\\d');
442 // Replace any standalone digit (not the one in d{}) with \d
443 numberPattern = numberPattern.replace(
444 i18n.phonenumbers.AsYouTypeFormatter.STANDALONE_DIGIT_PATTERN_, '\\d');
445 this.formattingTemplate_.clear();
446 /** @type {string} */
447 var tempTemplate = this.getFormattingTemplate_(numberPattern,
448 format.getFormatOrDefault());
449 if (tempTemplate.length > 0) {
450 this.formattingTemplate_.append(tempTemplate);
458 * Gets a formatting template which can be used to efficiently format a
459 * partial number where digits are added one by one.
461 * @param {string} numberPattern
462 * @param {string} numberFormat
466 i18n.phonenumbers.AsYouTypeFormatter.prototype.getFormattingTemplate_ =
467 function(numberPattern, numberFormat) {
469 // Creates a phone number consisting only of the digit 9 that matches the
470 // numberPattern by applying the pattern to the longestPhoneNumber string.
471 /** @type {string} */
472 var longestPhoneNumber = '999999999999999';
473 /** @type {Array.<string>} */
474 var m = longestPhoneNumber.match(numberPattern);
475 // this match will always succeed
476 /** @type {string} */
477 var aPhoneNumber = m[0];
478 // No formatting template can be created if the number of digits entered so
479 // far is longer than the maximum the current formatting rule can accommodate.
480 if (aPhoneNumber.length < this.nationalNumber_.getLength()) {
483 // Formats the number according to numberFormat
484 /** @type {string} */
485 var template = aPhoneNumber.replace(new RegExp(numberPattern, 'g'),
487 // Replaces each digit with character DIGIT_PLACEHOLDER
488 template = template.replace(new RegExp('9', 'g'), this.DIGIT_PLACEHOLDER_);
494 * Clears the internal state of the formatter, so it can be reused.
496 i18n.phonenumbers.AsYouTypeFormatter.prototype.clear = function() {
497 this.currentOutput_ = '';
498 this.accruedInput_.clear();
499 this.accruedInputWithoutFormatting_.clear();
500 this.formattingTemplate_.clear();
501 this.lastMatchPosition_ = 0;
502 this.currentFormattingPattern_ = '';
503 this.prefixBeforeNationalNumber_.clear();
504 this.nationalPrefixExtracted_ = '';
505 this.nationalNumber_.clear();
506 this.ableToFormat_ = true;
507 this.inputHasFormatting_ = false;
508 this.positionToRemember_ = 0;
509 this.originalPosition_ = 0;
510 this.isCompleteNumber_ = false;
511 this.isExpectingCountryCallingCode_ = false;
512 this.possibleFormats_ = [];
513 this.shouldAddSpaceAfterNationalPrefix_ = false;
514 if (this.currentMetaData_ != this.defaultMetaData_) {
515 this.currentMetaData_ = this.getMetadataForRegion_(this.defaultCountry_);
521 * Formats a phone number on-the-fly as each digit is entered.
523 * @param {string} nextChar the most recently entered digit of a phone number.
524 * Formatting characters are allowed, but as soon as they are encountered
525 * this method formats the number as entered and not 'as you type' anymore.
526 * Full width digits and Arabic-indic digits are allowed, and will be shown
528 * @return {string} the partially formatted phone number.
530 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigit = function(nextChar) {
531 this.currentOutput_ =
532 this.inputDigitWithOptionToRememberPosition_(nextChar, false);
533 return this.currentOutput_;
538 * Same as {@link #inputDigit}, but remembers the position where
539 * {@code nextChar} is inserted, so that it can be retrieved later by using
540 * {@link #getRememberedPosition}. The remembered position will be automatically
541 * adjusted if additional formatting characters are later inserted/removed in
542 * front of {@code nextChar}.
544 * @param {string} nextChar
547 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitAndRememberPosition =
550 this.currentOutput_ =
551 this.inputDigitWithOptionToRememberPosition_(nextChar, true);
552 return this.currentOutput_;
557 * @param {string} nextChar
558 * @param {boolean} rememberPosition
562 i18n.phonenumbers.AsYouTypeFormatter.prototype.
563 inputDigitWithOptionToRememberPosition_ = function(nextChar,
566 this.accruedInput_.append(nextChar);
567 if (rememberPosition) {
568 this.originalPosition_ = this.accruedInput_.getLength();
570 // We do formatting on-the-fly only when each character entered is either a
571 // digit, or a plus sign (accepted at the start of the number only).
572 if (!this.isDigitOrLeadingPlusSign_(nextChar)) {
573 this.ableToFormat_ = false;
574 this.inputHasFormatting_ = true;
576 nextChar = this.normalizeAndAccrueDigitsAndPlusSign_(nextChar,
579 if (!this.ableToFormat_) {
580 // When we are unable to format because of reasons other than that
581 // formatting chars have been entered, it can be due to really long IDDs or
582 // NDDs. If that is the case, we might be able to do formatting again after
584 if (this.inputHasFormatting_) {
585 return this.accruedInput_.toString();
586 } else if (this.attemptToExtractIdd_()) {
587 if (this.attemptToExtractCountryCallingCode_()) {
588 return this.attemptToChoosePatternWithPrefixExtracted_();
590 } else if (this.ableToExtractLongerNdd_()) {
591 // Add an additional space to separate long NDD and national significant
592 // number for readability. We don't set shouldAddSpaceAfterNationalPrefix_
593 // to true, since we don't want this to change later when we choose
594 // formatting templates.
595 this.prefixBeforeNationalNumber_.append(
596 i18n.phonenumbers.AsYouTypeFormatter.
597 SEPARATOR_BEFORE_NATIONAL_NUMBER_);
598 return this.attemptToChoosePatternWithPrefixExtracted_();
600 return this.accruedInput_.toString();
603 // We start to attempt to format only when at least MIN_LEADING_DIGITS_LENGTH
604 // digits (the plus sign is counted as a digit as well for this purpose) have
606 switch (this.accruedInputWithoutFormatting_.getLength()) {
610 return this.accruedInput_.toString();
612 if (this.attemptToExtractIdd_()) {
613 this.isExpectingCountryCallingCode_ = true;
615 // No IDD or plus sign is found, might be entering in national format.
616 this.nationalPrefixExtracted_ =
617 this.removeNationalPrefixFromNationalNumber_();
618 return this.attemptToChooseFormattingPattern_();
621 if (this.isExpectingCountryCallingCode_) {
622 if (this.attemptToExtractCountryCallingCode_()) {
623 this.isExpectingCountryCallingCode_ = false;
625 return this.prefixBeforeNationalNumber_.toString() +
626 this.nationalNumber_.toString();
628 if (this.possibleFormats_.length > 0) {
629 // The formatting pattern is already chosen.
630 /** @type {string} */
631 var tempNationalNumber = this.inputDigitHelper_(nextChar);
632 // See if the accrued digits can be formatted properly already. If not,
633 // use the results from inputDigitHelper, which does formatting based on
634 // the formatting pattern chosen.
635 /** @type {string} */
636 var formattedNumber = this.attemptToFormatAccruedDigits_();
637 if (formattedNumber.length > 0) {
638 return formattedNumber;
640 this.narrowDownPossibleFormats_(this.nationalNumber_.toString());
641 if (this.maybeCreateNewTemplate_()) {
642 return this.inputAccruedNationalNumber_();
644 return this.ableToFormat_ ?
645 this.appendNationalNumber_(tempNationalNumber) :
646 this.accruedInput_.toString();
648 return this.attemptToChooseFormattingPattern_();
658 i18n.phonenumbers.AsYouTypeFormatter.prototype.
659 attemptToChoosePatternWithPrefixExtracted_ = function() {
661 this.ableToFormat_ = true;
662 this.isExpectingCountryCallingCode_ = false;
663 this.possibleFormats_ = [];
664 return this.attemptToChooseFormattingPattern_();
669 * Some national prefixes are a substring of others. If extracting the shorter
670 * NDD doesn't result in a number we can format, we try to see if we can extract
671 * a longer version here.
675 i18n.phonenumbers.AsYouTypeFormatter.prototype.ableToExtractLongerNdd_ =
677 if (this.nationalPrefixExtracted_.length > 0) {
678 // Put the extracted NDD back to the national number before attempting to
679 // extract a new NDD.
680 /** @type {string} */
681 var nationalNumberStr = this.nationalNumber_.toString();
682 this.nationalNumber_.clear();
683 this.nationalNumber_.append(this.nationalPrefixExtracted_);
684 this.nationalNumber_.append(nationalNumberStr);
685 // Remove the previously extracted NDD from prefixBeforeNationalNumber. We
686 // cannot simply set it to empty string because people sometimes enter
687 // national prefix after country code, e.g +44 (0)20-1234-5678.
688 /** @type {string} */
689 var prefixBeforeNationalNumberStr =
690 this.prefixBeforeNationalNumber_.toString();
691 /** @type {number} */
692 var indexOfPreviousNdd = prefixBeforeNationalNumberStr.lastIndexOf(
693 this.nationalPrefixExtracted_);
694 this.prefixBeforeNationalNumber_.clear();
695 this.prefixBeforeNationalNumber_.append(
696 prefixBeforeNationalNumberStr.substring(0, indexOfPreviousNdd));
698 return this.nationalPrefixExtracted_ !=
699 this.removeNationalPrefixFromNationalNumber_();
704 * @param {string} nextChar
708 i18n.phonenumbers.AsYouTypeFormatter.prototype.isDigitOrLeadingPlusSign_ =
710 return i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN
712 (this.accruedInput_.getLength() == 1 &&
713 i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN.test(nextChar));
718 * Check to see if there is an exact pattern match for these digits. If so, we
719 * should use this instead of any other formatting template whose
720 * leadingDigitsPattern also matches the input.
724 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToFormatAccruedDigits_ =
727 /** @type {string} */
728 var nationalNumber = this.nationalNumber_.toString();
729 /** @type {number} */
730 var possibleFormatsLength = this.possibleFormats_.length;
731 for (var i = 0; i < possibleFormatsLength; ++i) {
732 /** @type {i18n.phonenumbers.NumberFormat} */
733 var numberFormat = this.possibleFormats_[i];
734 /** @type {string} */
735 var pattern = numberFormat.getPatternOrDefault();
736 /** @type {RegExp} */
737 var patternRegExp = new RegExp('^(?:' + pattern + ')$');
738 if (patternRegExp.test(nationalNumber)) {
739 this.shouldAddSpaceAfterNationalPrefix_ =
740 i18n.phonenumbers.AsYouTypeFormatter.
741 NATIONAL_PREFIX_SEPARATORS_PATTERN_.test(
742 numberFormat.getNationalPrefixFormattingRule());
743 /** @type {string} */
744 var formattedNumber = nationalNumber.replace(new RegExp(pattern, 'g'),
745 numberFormat.getFormat());
746 return this.appendNationalNumber_(formattedNumber);
754 * Combines the national number with any prefix (IDD/+ and country code or
755 * national prefix) that was collected. A space will be inserted between them if
756 * the current formatting template indicates this to be suitable.
757 * @param {string} nationalNumber
761 i18n.phonenumbers.AsYouTypeFormatter.prototype.appendNationalNumber_ =
762 function(nationalNumber) {
763 /** @type {number} */
764 var prefixBeforeNationalNumberLength =
765 this.prefixBeforeNationalNumber_.getLength();
766 if (this.shouldAddSpaceAfterNationalPrefix_ &&
767 prefixBeforeNationalNumberLength > 0 &&
768 this.prefixBeforeNationalNumber_.toString().charAt(
769 prefixBeforeNationalNumberLength - 1) !=
770 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_) {
771 // We want to add a space after the national prefix if the national prefix
772 // formatting rule indicates that this would normally be done, with the
773 // exception of the case where we already appended a space because the NDD
774 // was surprisingly long.
775 return this.prefixBeforeNationalNumber_ +
776 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_ +
779 return this.prefixBeforeNationalNumber_ + nationalNumber;
785 * Returns the current position in the partially formatted phone number of the
786 * character which was previously passed in as the parameter of
787 * {@link #inputDigitAndRememberPosition}.
791 i18n.phonenumbers.AsYouTypeFormatter.prototype.getRememberedPosition =
794 if (!this.ableToFormat_) {
795 return this.originalPosition_;
797 /** @type {number} */
798 var accruedInputIndex = 0;
799 /** @type {number} */
800 var currentOutputIndex = 0;
801 /** @type {string} */
802 var accruedInputWithoutFormatting =
803 this.accruedInputWithoutFormatting_.toString();
804 /** @type {string} */
805 var currentOutput = this.currentOutput_.toString();
806 while (accruedInputIndex < this.positionToRemember_ &&
807 currentOutputIndex < currentOutput.length) {
808 if (accruedInputWithoutFormatting.charAt(accruedInputIndex) ==
809 currentOutput.charAt(currentOutputIndex)) {
812 currentOutputIndex++;
814 return currentOutputIndex;
819 * Attempts to set the formatting template and returns a string which contains
820 * the formatted version of the digits entered so far.
825 i18n.phonenumbers.AsYouTypeFormatter.prototype.
826 attemptToChooseFormattingPattern_ = function() {
828 /** @type {string} */
829 var nationalNumber = this.nationalNumber_.toString();
830 // We start to attempt to format only when as least MIN_LEADING_DIGITS_LENGTH
831 // digits of national number (excluding national prefix) have been entered.
832 if (nationalNumber.length >=
833 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_) {
834 this.getAvailableFormats_(
835 nationalNumber.substring(0,
836 i18n.phonenumbers.AsYouTypeFormatter.MIN_LEADING_DIGITS_LENGTH_));
837 return this.maybeCreateNewTemplate_() ?
838 this.inputAccruedNationalNumber_() : this.accruedInput_.toString();
840 return this.appendNationalNumber_(nationalNumber);
846 * Invokes inputDigitHelper on each digit of the national number accrued, and
847 * returns a formatted string in the end.
852 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputAccruedNationalNumber_ =
855 /** @type {string} */
856 var nationalNumber = this.nationalNumber_.toString();
857 /** @type {number} */
858 var lengthOfNationalNumber = nationalNumber.length;
859 if (lengthOfNationalNumber > 0) {
860 /** @type {string} */
861 var tempNationalNumber = '';
862 for (var i = 0; i < lengthOfNationalNumber; i++) {
864 this.inputDigitHelper_(nationalNumber.charAt(i));
866 return this.ableToFormat_ ?
867 this.appendNationalNumber_(tempNationalNumber) :
868 this.accruedInput_.toString();
870 return this.prefixBeforeNationalNumber_.toString();
876 * Returns true if the current country is a NANPA country and the national
877 * number begins with the national prefix.
881 i18n.phonenumbers.AsYouTypeFormatter.prototype.
882 isNanpaNumberWithNationalPrefix_ = function() {
883 // For NANPA numbers beginning with 1[2-9], treat the 1 as the national
884 // prefix. The reason is that national significant numbers in NANPA always
885 // start with [2-9] after the national prefix. Numbers beginning with 1[01]
886 // can only be short/emergency numbers, which don't need the national prefix.
887 if (this.currentMetaData_.getCountryCode() != 1) {
890 /** @type {string} */
891 var nationalNumber = this.nationalNumber_.toString();
892 return (nationalNumber.charAt(0) == '1') &&
893 (nationalNumber.charAt(1) != '0') &&
894 (nationalNumber.charAt(1) != '1');
899 * Returns the national prefix extracted, or an empty string if it is not
904 i18n.phonenumbers.AsYouTypeFormatter.prototype.
905 removeNationalPrefixFromNationalNumber_ = function() {
907 /** @type {string} */
908 var nationalNumber = this.nationalNumber_.toString();
909 /** @type {number} */
910 var startOfNationalNumber = 0;
911 if (this.isNanpaNumberWithNationalPrefix_()) {
912 startOfNationalNumber = 1;
913 this.prefixBeforeNationalNumber_.append('1').append(
914 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
915 this.isCompleteNumber_ = true;
916 } else if (this.currentMetaData_.hasNationalPrefixForParsing()) {
917 /** @type {RegExp} */
918 var nationalPrefixForParsing = new RegExp(
919 '^(?:' + this.currentMetaData_.getNationalPrefixForParsing() + ')');
920 /** @type {Array.<string>} */
921 var m = nationalNumber.match(nationalPrefixForParsing);
922 if (m != null && m[0] != null && m[0].length > 0) {
923 // When the national prefix is detected, we use international formatting
924 // rules instead of national ones, because national formatting rules could
925 // contain local formatting rules for numbers entered without area code.
926 this.isCompleteNumber_ = true;
927 startOfNationalNumber = m[0].length;
928 this.prefixBeforeNationalNumber_.append(nationalNumber.substring(0,
929 startOfNationalNumber));
932 this.nationalNumber_.clear();
933 this.nationalNumber_.append(nationalNumber.substring(startOfNationalNumber));
934 return nationalNumber.substring(0, startOfNationalNumber);
939 * Extracts IDD and plus sign to prefixBeforeNationalNumber when they are
940 * available, and places the remaining input into nationalNumber.
942 * @return {boolean} true when accruedInputWithoutFormatting begins with the
943 * plus sign or valid IDD for defaultCountry.
946 i18n.phonenumbers.AsYouTypeFormatter.prototype.attemptToExtractIdd_ =
949 /** @type {string} */
950 var accruedInputWithoutFormatting =
951 this.accruedInputWithoutFormatting_.toString();
952 /** @type {RegExp} */
953 var internationalPrefix = new RegExp(
954 '^(?:' + '\\' + i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + '|' +
955 this.currentMetaData_.getInternationalPrefix() + ')');
956 /** @type {Array.<string>} */
957 var m = accruedInputWithoutFormatting.match(internationalPrefix);
958 if (m != null && m[0] != null && m[0].length > 0) {
959 this.isCompleteNumber_ = true;
960 /** @type {number} */
961 var startOfCountryCallingCode = m[0].length;
962 this.nationalNumber_.clear();
963 this.nationalNumber_.append(
964 accruedInputWithoutFormatting.substring(startOfCountryCallingCode));
965 this.prefixBeforeNationalNumber_.clear();
966 this.prefixBeforeNationalNumber_.append(
967 accruedInputWithoutFormatting.substring(0, startOfCountryCallingCode));
968 if (accruedInputWithoutFormatting.charAt(0) !=
969 i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
970 this.prefixBeforeNationalNumber_.append(
971 i18n.phonenumbers.AsYouTypeFormatter.
972 SEPARATOR_BEFORE_NATIONAL_NUMBER_);
981 * Extracts the country calling code from the beginning of nationalNumber to
982 * prefixBeforeNationalNumber when they are available, and places the remaining
983 * input into nationalNumber.
985 * @return {boolean} true when a valid country calling code can be found.
988 i18n.phonenumbers.AsYouTypeFormatter.prototype.
989 attemptToExtractCountryCallingCode_ = function() {
991 if (this.nationalNumber_.getLength() == 0) {
994 /** @type {!goog.string.StringBuffer} */
995 var numberWithoutCountryCallingCode = new goog.string.StringBuffer();
996 /** @type {number} */
997 var countryCode = this.phoneUtil_.extractCountryCode(
998 this.nationalNumber_, numberWithoutCountryCallingCode);
999 if (countryCode == 0) {
1002 this.nationalNumber_.clear();
1003 this.nationalNumber_.append(numberWithoutCountryCallingCode.toString());
1004 /** @type {string} */
1005 var newRegionCode = this.phoneUtil_.getRegionCodeForCountryCode(countryCode);
1006 if (i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY ==
1008 this.currentMetaData_ =
1009 this.phoneUtil_.getMetadataForNonGeographicalRegion(countryCode);
1010 } else if (newRegionCode != this.defaultCountry_) {
1011 this.currentMetaData_ = this.getMetadataForRegion_(newRegionCode);
1013 /** @type {string} */
1014 var countryCodeString = '' + countryCode;
1015 this.prefixBeforeNationalNumber_.append(countryCodeString).append(
1016 i18n.phonenumbers.AsYouTypeFormatter.SEPARATOR_BEFORE_NATIONAL_NUMBER_);
1022 * Accrues digits and the plus sign to accruedInputWithoutFormatting for later
1023 * use. If nextChar contains a digit in non-ASCII format (e.g. the full-width
1024 * version of digits), it is first normalized to the ASCII version. The return
1025 * value is nextChar itself, or its normalized version, if nextChar is a digit
1026 * in non-ASCII format. This method assumes its input is either a digit or the
1029 * @param {string} nextChar
1030 * @param {boolean} rememberPosition
1034 i18n.phonenumbers.AsYouTypeFormatter.prototype.
1035 normalizeAndAccrueDigitsAndPlusSign_ = function(nextChar,
1038 /** @type {string} */
1040 if (nextChar == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
1041 normalizedChar = nextChar;
1042 this.accruedInputWithoutFormatting_.append(nextChar);
1044 normalizedChar = i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS[nextChar];
1045 this.accruedInputWithoutFormatting_.append(normalizedChar);
1046 this.nationalNumber_.append(normalizedChar);
1048 if (rememberPosition) {
1049 this.positionToRemember_ = this.accruedInputWithoutFormatting_.getLength();
1051 return normalizedChar;
1056 * @param {string} nextChar
1060 i18n.phonenumbers.AsYouTypeFormatter.prototype.inputDigitHelper_ =
1061 function(nextChar) {
1063 /** @type {string} */
1064 var formattingTemplate = this.formattingTemplate_.toString();
1065 if (formattingTemplate.substring(this.lastMatchPosition_)
1066 .search(this.DIGIT_PATTERN_) >= 0) {
1067 /** @type {number} */
1068 var digitPatternStart = formattingTemplate.search(this.DIGIT_PATTERN_);
1069 /** @type {string} */
1071 formattingTemplate.replace(this.DIGIT_PATTERN_, nextChar);
1072 this.formattingTemplate_.clear();
1073 this.formattingTemplate_.append(tempTemplate);
1074 this.lastMatchPosition_ = digitPatternStart;
1075 return tempTemplate.substring(0, this.lastMatchPosition_ + 1);
1077 if (this.possibleFormats_.length == 1) {
1078 // More digits are entered than we could handle, and there are no other
1079 // valid patterns to try.
1080 this.ableToFormat_ = false;
1081 } // else, we just reset the formatting pattern.
1082 this.currentFormattingPattern_ = '';
1083 return this.accruedInput_.toString();