You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2679 lines
104 KiB

/*
* @license
* Copyright (C) 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Utility for international phone numbers.
* Functionality includes formatting, parsing and validation.
* (based on the java implementation).
*
* @author Nikolaos Trogkanis
*/
goog.provide('i18n.phonenumbers.PhoneNumberUtil');
goog.require('goog.array');
goog.require('goog.proto2.PbLiteSerializer');
goog.require('goog.string');
goog.require('goog.string.StringBuffer');
goog.require('i18n.phonenumbers.NumberFormat');
goog.require('i18n.phonenumbers.PhoneMetadata');
goog.require('i18n.phonenumbers.PhoneMetadataCollection');
goog.require('i18n.phonenumbers.PhoneNumber');
goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource');
goog.require('i18n.phonenumbers.PhoneNumberDesc');
goog.require('i18n.phonenumbers.metadata');
/**
* @constructor
* @private
*/
i18n.phonenumbers.PhoneNumberUtil = function() {
/**
* A mapping from a region code to the PhoneMetadata for that region.
* @type {Object.<string, i18n.phonenumbers.PhoneMetadata>}
*/
this.countryToMetadata = {};
};
goog.addSingletonGetter(i18n.phonenumbers.PhoneNumberUtil);
/**
* Errors encountered when parsing phone numbers.
*
* @enum {string}
*/
i18n.phonenumbers.Error = {
INVALID_COUNTRY_CODE: 'Invalid country code',
// This generally indicates the string passed in had less than 3 digits in it.
// More specifically, the number failed to match the regular expression
// VALID_PHONE_NUMBER.
NOT_A_NUMBER: 'The string supplied did not seem to be a phone number',
// This indicates the string started with an international dialing prefix, but
// after this was stripped from the number, had less digits than any valid
// phone number (including country code) could have.
TOO_SHORT_AFTER_IDD: 'Phone number too short after IDD',
// This indicates the string, after any country code has been stripped, had
// less digits than any
// valid phone number could have.
TOO_SHORT_NSN: 'The string supplied is too short to be a phone number',
// This indicates the string had more digits than any valid phone number could
// have.
TOO_LONG: 'The string supplied is too long to be a phone number'
}
/**
* @const
* @type {number}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.NANPA_COUNTRY_CODE_ = 1;
/**
* The minimum length of the national significant number.
*
* @const
* @type {number}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_ = 3;
/**
* The maximum length of the national significant number.
*
* @const
* @type {number}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_FOR_NSN_ = 15;
/**
* The PLUS_SIGN signifies the international prefix.
*
* @const
* @type {string}
*/
i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN = '+';
/**
* These mappings map a character (key) to a specific digit that should replace
* it for normalization purposes. Non-European digits that may be used in phone
* numbers are mapped to a European equivalent.
*
* @const
*/
i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS = {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'\uFF10': '0', // Fullwidth digit 0
'\uFF11': '1', // Fullwidth digit 1
'\uFF12': '2', // Fullwidth digit 2
'\uFF13': '3', // Fullwidth digit 3
'\uFF14': '4', // Fullwidth digit 4
'\uFF15': '5', // Fullwidth digit 5
'\uFF16': '6', // Fullwidth digit 6
'\uFF17': '7', // Fullwidth digit 7
'\uFF18': '8', // Fullwidth digit 8
'\uFF19': '9', // Fullwidth digit 9
'\u0660': '0', // Arabic-indic digit 0
'\u0661': '1', // Arabic-indic digit 1
'\u0662': '2', // Arabic-indic digit 2
'\u0663': '3', // Arabic-indic digit 3
'\u0664': '4', // Arabic-indic digit 4
'\u0665': '5', // Arabic-indic digit 5
'\u0666': '6', // Arabic-indic digit 6
'\u0667': '7', // Arabic-indic digit 7
'\u0668': '8', // Arabic-indic digit 8
'\u0669': '9' // Arabic-indic digit 9
};
/**
* Only upper-case variants of alpha characters are stored.
*
* @const
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.ALPHA_MAPPINGS_ = {
'A': '2',
'B': '2',
'C': '2',
'D': '3',
'E': '3',
'F': '3',
'G': '4',
'H': '4',
'I': '4',
'J': '5',
'K': '5',
'L': '5',
'M': '6',
'N': '6',
'O': '6',
'P': '7',
'Q': '7',
'R': '7',
'S': '7',
'T': '8',
'U': '8',
'V': '8',
'W': '9',
'X': '9',
'Y': '9',
'Z': '9'
};
/**
* For performance reasons, amalgamate both into one map.
*
* @const
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.ALL_NORMALIZATION_MAPPINGS_ = {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9',
'\uFF10': '0', // Fullwidth digit 0
'\uFF11': '1', // Fullwidth digit 1
'\uFF12': '2', // Fullwidth digit 2
'\uFF13': '3', // Fullwidth digit 3
'\uFF14': '4', // Fullwidth digit 4
'\uFF15': '5', // Fullwidth digit 5
'\uFF16': '6', // Fullwidth digit 6
'\uFF17': '7', // Fullwidth digit 7
'\uFF18': '8', // Fullwidth digit 8
'\uFF19': '9', // Fullwidth digit 9
'\u0660': '0', // Arabic-indic digit 0
'\u0661': '1', // Arabic-indic digit 1
'\u0662': '2', // Arabic-indic digit 2
'\u0663': '3', // Arabic-indic digit 3
'\u0664': '4', // Arabic-indic digit 4
'\u0665': '5', // Arabic-indic digit 5
'\u0666': '6', // Arabic-indic digit 6
'\u0667': '7', // Arabic-indic digit 7
'\u0668': '8', // Arabic-indic digit 8
'\u0669': '9', // Arabic-indic digit 9
'A': '2',
'B': '2',
'C': '2',
'D': '3',
'E': '3',
'F': '3',
'G': '4',
'H': '4',
'I': '4',
'J': '5',
'K': '5',
'L': '5',
'M': '6',
'N': '6',
'O': '6',
'P': '7',
'Q': '7',
'R': '7',
'S': '7',
'T': '8',
'U': '8',
'V': '8',
'W': '9',
'X': '9',
'Y': '9',
'Z': '9'
};
/**
* A list of all country codes where national significant numbers (excluding any
* national prefix) exist that start with a leading zero.
*
* @const
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.LEADING_ZERO_COUNTRIES_ = {
39: 1, // Italy
47: 1, // Norway
225: 1, // Cote d'Ivoire
227: 1, // Niger
228: 1, // Togo
241: 1, // Gabon
379: 1 // Vatican City
};
/**
* Pattern that makes it easy to distinguish whether a country has a unique
* international dialing prefix or not. If a country has a unique international
* prefix (e.g. 011 in USA), it will be represented as a string that contains a
* sequence of ASCII digits. If there are multiple available international
* prefixes in a country, they will be represented as a regex string that always
* contains character(s) other than ASCII digits. Note this regex also includes
* tilde, which signals waiting for the tone.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.UNIQUE_INTERNATIONAL_PREFIX_ =
/[\d]+(?:[~\u2053\u223C\uFF5E][\d]+)?/;
/**
* Regular expression of acceptable punctuation found in phone numbers. This
* excludes punctuation found as a leading character only. This consists of dash
* characters, white space characters, full stops, slashes, square brackets,
* parentheses and tildes. It also includes the letter 'x' as that is found as a
* placeholder for carrier information in some phone numbers.
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION_ =
'-x\u2010-\u2015\u2212\uFF0D-\uFF0F \u00A0\u200B\u2060\u3000()' +
'\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E';
/**
* Digits accepted in phone numbers (ascii, fullwidth, and arabic-indic digits).
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ =
'0-9\uFF10-\uFF19\u0660-\u0669';
/**
* We accept alpha characters in phone numbers, ASCII only, upper and lower
* case.
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_ = 'A-Za-z';
/**
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ = '+\uFF0B';
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN_ =
new RegExp('^[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + ']+');
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN_ =
new RegExp('([' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + '])');
/**
* Regular expression of acceptable characters that may start a phone number for
* the purposes of parsing. This allows us to strip away meaningless prefixes to
* phone numbers that may be mistakenly given to us. This consists of digits,
* the plus symbol and arabic-indic digits. This does not contain alpha
* characters, although they may be used later in the number. It also does not
* include other punctuation, as this will be stripped later during parsing and
* is of no information value when parsing a number.
*
* @const
* @type {RegExp}
* @protected
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_START_CHAR_PATTERN =
new RegExp('[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ +
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']');
/**
* Regular expression of characters typically used to start a second phone
* number for the purposes of parsing. This allows us to strip off parts of the
* number that are actually the start of another number, such as for:
* (530) 583-6985 x302/x2303 -> the second extension here makes this actually
* two phone numbers, (530) 583-6985 x302 and (530) 583-6985 x2303. We remove
* the second extension so that the first number is parsed correctly.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.SECOND_NUMBER_START_PATTERN_ = /[\\\/] *x/;
/**
* Regular expression of trailing characters that we want to remove. We remove
* all characters that are not alpha or numerical characters. The hash character
* is retained here, as it may signify the previous block was an extension.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN_ =
new RegExp('[^' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ +
i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_ + '#]+$');
/**
* We use this pattern to check if the phone number has at least three letters
* in it - if so, then we treat it as a number where some phone-number digits
* are represented by letters.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_PHONE_PATTERN_ =
/(?:.*?[A-Za-z]){3}.*/;
/**
* Regular expression of viable phone numbers. This is location independent.
* Checks we have at least three leading digits, and only valid punctuation,
* alpha characters and digits in the phone number. Does not include extension
* data. The symbol 'x' is allowed here as valid punctuation since it is often
* used as a placeholder for carrier codes, for example in Brazilian phone
* numbers. We also allow multiple '+' characters at the start.
* Corresponds to the following:
* plus_sign*([punctuation]*[digits]){3,}([punctuation]|[digits]|[alpha])*
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_ =
'[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + ']*(?:[' +
i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION_ + ']*[' +
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']){3,}[' +
i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_ +
i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION_ +
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']*';
/**
* Default extension prefix to use when formatting. This will be put in front of
* any extension component of the number, after the main national number is
* formatted. For example, if you wish the default extension formatting to be
* ' extn: 3456', then you should specify ' extn: ' here as the default
* extension prefix. This can be overridden by country-specific preferences.
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.DEFAULT_EXTN_PREFIX_ = ' ext. ';
/**
* Regexp of all possible ways to write extensions, for use when parsing. This
* will be run as a case-insensitive regexp match. Wide character versions are
* also provided after each ascii version. There are two regular expressions
* here: the more generic one starts with optional white space and ends with an
* optional full stop (.), followed by zero or more spaces/tabs and then the
* numbers themselves. The other one covers the special case of American numbers
* where the extension is written with a hash at the end, such as "- 503#". Note
* that the only capturing groups should be around the digits that you want to
* capture as part of the extension, or else parsing will fail!
*
* @const
* @type {string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.KNOWN_EXTN_PATTERNS_ =
'[ \u00A0\\t,]*' + '(?:ext(?:ensio)?n?|\uFF45\uFF58\uFF54\uFF4E?|' +
'[,x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)' +
'[:\\.\uFF0E]?[ \u00A0\\t,-]*([' +
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{1,7})#?|[- ]+([' +
i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{1,5})#';
/**
* Regexp of all known extension prefixes used by different countries followed
* by 1 or more valid digits, for use when parsing.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERN_ =
new RegExp('(?:' + i18n.phonenumbers.PhoneNumberUtil.KNOWN_EXTN_PATTERNS_ +
')$', 'i');
/**
* We append optionally the extension pattern to the end here, as a valid phone
* number may have an extension prefix appended, followed by 1 or more digits.
*
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_PATTERN_ =
new RegExp('^' + i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_ +
'(?:' + i18n.phonenumbers.PhoneNumberUtil.KNOWN_EXTN_PATTERNS_ +
')?' + '$', 'i');
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.NON_DIGITS_PATTERN_ = /\D+/;
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_PATTERN_ = /(\$1)/;
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.NP_PATTERN_ = /\$NP/;
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.FG_PATTERN_ = /\$FG/;
/**
* @const
* @type {RegExp}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.CC_PATTERN_ = /\$CC/;
/**
* INTERNATIONAL and NATIONAL formats are consistent with the definition in
* ITU-T Recommendation E. 123. For example, the number of the Google Zurich
* office will be written as "+41 44 668 1800" in INTERNATIONAL format, and as
* "044 668 1800" in NATIONAL format. E164 format is as per INTERNATIONAL format
* but with no formatting applied, e.g. +41446681800.
*
* @enum {number}
*/
i18n.phonenumbers.PhoneNumberFormat = {
E164: 0,
INTERNATIONAL: 1,
NATIONAL: 2
};
/**
* Type of phone numbers.
*
* @enum {number}
*/
i18n.phonenumbers.PhoneNumberType = {
FIXED_LINE: 0,
MOBILE: 1,
// In some countries (e.g. the USA), it is impossible to distinguish between
// fixed-line and mobile numbers by looking at the phone number itself.
FIXED_LINE_OR_MOBILE: 2,
// Freephone lines
TOLL_FREE: 3,
PREMIUM_RATE: 4,
// The cost of this call is shared between the caller and the recipient, and
// is hence typically less than PREMIUM_RATE calls. See
// http://en.wikipedia.org/wiki/Shared_Cost_Service for more information.
SHARED_COST: 5,
// Voice over IP numbers. This includes TSoIP (Telephony Service over IP).
VOIP: 6,
// A personal number is associated with a particular person, and may be routed
// to either a MOBILE or FIXED_LINE number. Some more information can be found
// here: http://en.wikipedia.org/wiki/Personal_Numbers
PERSONAL_NUMBER: 7,
PAGER: 8,
// A phone number is of type UNKNOWN when it does not fit any of the known
// patterns for a specific country.
UNKNOWN: 9
};
/**
* Types of phone number matches. See detailed description beside the
* isNumberMatch() method.
*
* @enum {number}
*/
i18n.phonenumbers.PhoneNumberUtil.MatchType = {
NO_MATCH: 0,
SHORT_NSN_MATCH: 1,
NSN_MATCH: 2,
EXACT_MATCH: 3
};
/**
* Possible outcomes when testing if a PhoneNumber is possible.
*
* @enum {number}
*/
i18n.phonenumbers.PhoneNumberUtil.ValidationResult = {
IS_POSSIBLE: 0,
INVALID_COUNTRY_CODE: 1,
TOO_SHORT: 2,
TOO_LONG: 3
};
/**
* Attempts to extract a possible number from the string passed in. This
* currently strips all leading characters that could not be used to start a
* phone number. Characters that can be used to start a phone number are defined
* in the VALID_START_CHAR_PATTERN. If none of these characters are found in the
* number passed in, an empty string is returned. This function also attempts to
* strip off any alternative extensions or endings if two or more are present,
* such as in the case of: (530) 583-6985 x302/x2303. The second extension here
* makes this actually two phone numbers, (530) 583-6985 x302 and (530) 583-6985
* x2303. We remove the second extension so that the first number is parsed
* correctly.
*
* @param {string} number the string that might contain a phone number.
* @return {string} the number, stripped of any non-phone-number prefix (such as
* 'Tel:') or an empty string if no character used to start phone numbers
* (such as + or any digit) is found in the number.
*/
i18n.phonenumbers.PhoneNumberUtil.extractPossibleNumber = function(number) {
/** @type {string} */
var possibleNumber;
/** @type {number} */
var start = number
.search(i18n.phonenumbers.PhoneNumberUtil.VALID_START_CHAR_PATTERN);
if (start >= 0) {
possibleNumber = number.substring(start);
// Remove trailing non-alpha non-numerical characters.
possibleNumber = possibleNumber.replace(
i18n.phonenumbers.PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN_, '');
// Check for extra numbers at the end.
/** @type {number} */
var secondNumberStart = possibleNumber
.search(i18n.phonenumbers.PhoneNumberUtil.SECOND_NUMBER_START_PATTERN_);
if (secondNumberStart >= 0) {
possibleNumber = possibleNumber.substring(0, secondNumberStart);
}
} else {
possibleNumber = '';
}
return possibleNumber;
};
/**
* Checks to see if the string of characters could possibly be a phone number at
* all. At the moment, checks to see that the string begins with at least 3
* digits, ignoring any punctuation commonly found in phone numbers. This method
* does not require the number to be normalized in advance - but does assume
* that leading non-number symbols have been removed, such as by the method
* extractPossibleNumber.
*
* @param {string} number string to be checked for viability as a phone number.
* @return {boolean} true if the number could be a phone number of some sort,
* otherwise false.
*/
i18n.phonenumbers.PhoneNumberUtil.isViablePhoneNumber = function(number) {
if (number.length < i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_) {
return false;
}
return i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_PATTERN_, number);
};
/**
* Normalizes a string of characters representing a phone number. This performs
* the following conversions:
* - Wide-ascii digits are converted to normal ASCII (European) digits.
* - Letters are converted to their numeric representation on a telephone
* keypad. The keypad used here is the one defined in ITU Recommendation E.161.
* This is only done if there are 3 or more letters in the number, to lessen the
* risk that such letters are typos - otherwise alpha characters are stripped.
* - Punctuation is stripped.
* - Arabic-Indic numerals are converted to European numerals.
*
* @param {string} number a string of characters representing a phone number.
* @return {string} the normalized string version of the phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.normalize = function(number) {
if (i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_PHONE_PATTERN_, number)) {
return i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_(number,
i18n.phonenumbers.PhoneNumberUtil.ALL_NORMALIZATION_MAPPINGS_, true);
} else {
return i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_(number,
i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS, true);
}
};
/**
* Normalizes a string of characters representing a phone number. This is a
* wrapper for normalize(String number) but does in-place normalization of the
* StringBuffer provided.
*
* @param {!goog.string.StringBuffer} number a StringBuffer of characters
* representing a phone number that will be normalized in place.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.normalizeSB_ = function(number) {
/** @type {string} */
var normalizedNumber = i18n.phonenumbers.PhoneNumberUtil.normalize(number
.toString());
number.clear();
number.append(normalizedNumber);
};
/**
* Normalizes a string of characters representing a phone number. This converts
* wide-ascii and arabic-indic numerals to European numerals, and strips
* punctuation and alpha characters.
*
* @param {string} number a string of characters representing a phone number.
* @return {string} the normalized string version of the phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.normalizeDigitsOnly = function(number) {
return i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_(number,
i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS, true);
};
/**
* Converts all alpha characters in a number to their respective digits on a
* keypad, but retains existing formatting. Also converts wide-ascii digits to
* normal ascii digits, and converts Arabic-Indic numerals to European numerals.
*
* @param {string} number a string of characters representing a phone number.
* @return {string} the normalized string version of the phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.convertAlphaCharactersInNumber =
function(number) {
return i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_(number,
i18n.phonenumbers.PhoneNumberUtil.ALL_NORMALIZATION_MAPPINGS_, false);
};
/**
* Gets the length of the geographical area code from the national_number field
* of the PhoneNumber object passed in, so that clients could use it to split a
* national significant number into geographical area code and subscriber
* number. It works in such a way that the resultant subscriber number should be
* diallable, at least on some devices. An example of how this could be used:
*
* var phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance();
* var number = phoneUtil.parse('16502530000', 'US');
* var nationalSignificantNumber =
* i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
* var areaCode;
* var subscriberNumber;
*
* var areaCodeLength = phoneUtil.getLengthOfGeographicalAreaCode(number);
* if (areaCodeLength > 0) {
* areaCode = nationalSignificantNumber.substring(0, areaCodeLength);
* subscriberNumber = nationalSignificantNumber.substring(areaCodeLength);
* } else {
* areaCode = '';
* subscriberNumber = nationalSignificantNumber;
* }
*
* N.B.: area code is a very ambiguous concept, so the I18N team generally
* recommends against using it for most purposes, but recommends using the more
* general national_number instead. Read the following carefully before deciding
* to use this method:
* - geographical area codes change over time, and this method honors those
* changes; therefore, it doesn't guarantee the stability of the result it
* produces.
* - subscriber numbers may not be diallable from all devices (notably mobile
* devices, which typically requires the full national_number to be dialled in
* most countries).
* - most non-geographical numbers have no area codes.
* - some geographical numbers have no area codes.
*
* @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
* which clients want to know the length of the area code in the
* national_number field.
* @return {number} the length of area code of the PhoneNumber object passed in.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getLengthOfGeographicalAreaCode =
function(number) {
if (number == null) {
return 0;
}
/** @type {string} */
var regionCode = /** @type {string} */ (this.getRegionCodeForNumber(number));
if (!this.isValidRegionCode_(regionCode)) {
return 0;
}
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
// For NANPA countries, national prefix is the same as country code, but it
// is not stored in
// the metadata.
if (!metadata.hasNationalPrefix() && !this.isNANPACountry(regionCode)) {
return 0;
}
/** @type {i18n.phonenumbers.PhoneNumberType} */
var type = this.getNumberTypeHelper_(
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number),
metadata);
// Most numbers other than the two types below have to be dialled in full.
if (type != i18n.phonenumbers.PhoneNumberType.FIXED_LINE &&
type != i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE) {
return 0;
}
/** @type {i18n.phonenumbers.PhoneNumber} */
var copiedProto;
if (number.hasExtension()) {
// We don't want to alter the proto given to us, but we don't want to
// include the extension when we format it, so we copy it and clear the
// extension here.
copiedProto = new i18n.phonenumbers.PhoneNumber();
copiedProto.mergeFrom(number);
copiedProto.clearExtension();
} else {
copiedProto = number;
}
/** @type {string} */
var nationalSignificantNumber = this.format(copiedProto,
i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL);
/** @type {!Array.<string>} */
var numberGroups = nationalSignificantNumber.split(
i18n.phonenumbers.PhoneNumberUtil.NON_DIGITS_PATTERN_);
// The pattern will start with '+COUNTRY_CODE ' so the first group will always
// be the empty string (before the + symbol) and the second group will be the
// country code. The third group will be area code if it's not the last group.
// NOTE: On IE the first group that is supposed to be the empty string does
// not appear in the array of number groups... so make the result on non-IE
// browsers to be that of IE.
if (numberGroups[0].length == 0) {
numberGroups.shift();
}
if (numberGroups.length <= 2) {
return 0;
}
return numberGroups[1].length;
};
/**
* Normalizes a string of characters representing a phone number by replacing
* all characters found in the accompanying map with the values therein, and
* stripping all other characters if removeNonMatches is true.
*
* @param {string} number a string of characters representing a phone number.
* @param {!Object} normalizationReplacements a mapping of characters to what
* they should be replaced by in the normalized version of the phone number.
* @param {boolean} removeNonMatches indicates whether characters that are not
* able to be replaced should be stripped from the number. If this is false,
* they will be left unchanged in the number.
* @return {string} the normalized string version of the phone number.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_ =
function(number, normalizationReplacements, removeNonMatches) {
/** @type {!goog.string.StringBuffer} */
var normalizedNumber = new goog.string.StringBuffer();
/** @type {string} */
var character;
/** @type {string} */
var newDigit;
/** @type {number} */
var numberLength = number.length;
for (var i = 0; i < numberLength; ++i) {
character = number.charAt(i);
newDigit = normalizationReplacements[character.toUpperCase()];
if (newDigit != null) {
normalizedNumber.append(newDigit);
} else if (!removeNonMatches) {
normalizedNumber.append(character);
}
// If neither of the above are true, we remove this character.
}
return normalizedNumber.toString();
};
/**
* Helper function to check region code is not unknown or null.
*
* @param {?string} regionCode the ISO 3166-1 two-letter country code that
* denotes the country/region that we want to get the country code for.
* @return {boolean} true if region code is valid.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isValidRegionCode_ =
function(regionCode) {
return regionCode != null &&
regionCode.toUpperCase() in i18n.phonenumbers.metadata.countryToMetadata;
};
/**
* Formats a phone number in the specified format using default rules. Note that
* this does not promise to produce a phone number that the user can dial from
* where they are - although we do format in either 'national' or
* 'international' format depending on what the client asks for, we do not
* currently support a more abbreviated format, such as for users in the same
* "area" who could potentially dial the number without area code. Note that if
* the phone number has a country code of 0 or an otherwise invalid country
* code, we cannot work out which formatting rules to apply so we return the
* national significant number with no formatting applied.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
* formatted.
* @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
* phone number should be formatted into.
* @return {string} the formatted phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.format =
function(number, numberFormat) {
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
/** @type {string} */
var nationalSignificantNumber = i18n.phonenumbers.PhoneNumberUtil
.getNationalSignificantNumber(number);
if (numberFormat == i18n.phonenumbers.PhoneNumberFormat.E164) {
// Early exit for E164 case since no formatting of the national number needs
// to be applied. Extensions are not formatted.
return this.formatNumberByFormat_(countryCode,
i18n.phonenumbers.PhoneNumberFormat.E164,
nationalSignificantNumber, '');
}
// Note getRegionCodeForCountryCode() is used because formatting information
// for countries which share a country code is contained by only one country
// for performance reasons. For example, for NANPA countries it will be
// contained in the metadata for US.
/** @type {string} */
var regionCode = this.getRegionCodeForCountryCode(countryCode);
if (!this.isValidRegionCode_(regionCode)) {
return nationalSignificantNumber;
}
/** @type {string} */
var formattedExtension = this.maybeGetFormattedExtension_(number, regionCode);
/** @type {string} */
var formattedNationalNumber =
this.formatNationalNumber_(nationalSignificantNumber,
regionCode,
numberFormat);
return this.formatNumberByFormat_(countryCode,
numberFormat,
formattedNationalNumber,
formattedExtension);
};
/**
* Formats a phone number in the specified format using client-defined
* formatting rules. Note that if the phone number has a country code of zero or
* an otherwise invalid country code, we cannot work out things like whether
* there should be a national prefix applied, or how to format extensions, so we
* return the national significant number with no formatting applied.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
* formatted.
* @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
* phone number should be formatted into.
* @param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting
* rules specified by clients.
* @return {string} the formatted phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatByPattern =
function(number, numberFormat, userDefinedFormats) {
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
/** @type {string} */
var nationalSignificantNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
// Note getRegionCodeForCountryCode() is used because formatting information
// for countries which share a country code is contained by only one country
// for performance reasons. For example, for NANPA countries it will be
// contained in the metadata for US.
/** @type {string} */
var regionCode = this.getRegionCodeForCountryCode(countryCode);
if (!this.isValidRegionCode_(regionCode)) {
return nationalSignificantNumber;
}
/** @type {Array.<i18n.phonenumbers.NumberFormat>} */
var userDefinedFormatsCopy = [];
/** @type {number} */
var size = userDefinedFormats.length;
for (var i = 0; i < size; ++i) {
/** @type {i18n.phonenumbers.NumberFormat} */
var numFormat = userDefinedFormats[i];
/** @type {string} */
var nationalPrefixFormattingRule =
numFormat.getNationalPrefixFormattingRuleOrDefault();
if (nationalPrefixFormattingRule.length > 0) {
// Before we do a replacement of the national prefix pattern $NP with the
// national prefix, we need to copy the rule so that subsequent
// replacements for different numbers have the appropriate national
// prefix.
/** type {i18n.phonenumbers.NumberFormat} */
var numFormatCopy = new i18n.phonenumbers.NumberFormat();
numFormatCopy.mergeFrom(numFormat);
/** @type {string} */
var nationalPrefix =
this.getMetadataForRegion(regionCode).getNationalPrefixOrDefault();
if (nationalPrefix.length > 0) {
// Replace $NP with national prefix and $FG with the first group ($1).
nationalPrefixFormattingRule = nationalPrefixFormattingRule
.replace(i18n.phonenumbers.PhoneNumberUtil.NP_PATTERN_,
nationalPrefix)
.replace(i18n.phonenumbers.PhoneNumberUtil.FG_PATTERN_, '$1');
numFormatCopy.setNationalPrefixFormattingRule(
nationalPrefixFormattingRule);
} else {
// We don't want to have a rule for how to format the national prefix if
// there isn't one.
numFormatCopy.clearNationalPrefixFormattingRule();
}
userDefinedFormatsCopy.push(numFormatCopy);
} else {
// Otherwise, we just add the original rule to the modified list of
// formats.
userDefinedFormatsCopy.push(numFormat);
}
}
/** @type {string} */
var formattedExtension = this.maybeGetFormattedExtension_(number, regionCode);
/** @type {string} */
var formattedNationalNumber =
this.formatAccordingToFormats_(nationalSignificantNumber,
userDefinedFormatsCopy,
numberFormat);
return this.formatNumberByFormat_(countryCode,
numberFormat,
formattedNationalNumber,
formattedExtension);
};
/**
* @param {i18n.phonenumbers.PhoneNumber} number
* @param {string} carrierCode
* @return {string}
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.
formatNationalNumberWithCarrierCode = function(number, carrierCode) {
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
/** @type {string} */
var nationalSignificantNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
// Note getRegionCodeForCountryCode() is used because formatting information
// for countries which share a country code is contained by only one country
// for performance reasons. For example, for NANPA countries it will be
// contained in the metadata for US.
/** @type {string} */
var regionCode = this.getRegionCodeForCountryCode(countryCode);
if (!this.isValidRegionCode_(regionCode)) {
return nationalSignificantNumber;
}
/** @type {string} */
var formattedExtension = this.maybeGetFormattedExtension_(number, regionCode);
/** @type {string} */
var formattedNationalNumber =
this.formatNationalNumber_(nationalSignificantNumber,
regionCode,
i18n.phonenumbers.PhoneNumberFormat.NATIONAL,
carrierCode);
return this.formatNumberByFormat_(
countryCode, i18n.phonenumbers.PhoneNumberFormat.NATIONAL,
formattedNationalNumber, formattedExtension);
};
/**
* Formats a phone number for out-of-country dialing purpose. If no
* countryCallingFrom is supplied, we format the number in its INTERNATIONAL
* format. If the countryCallingFrom is the same as the country where the number
* is from, then NATIONAL formatting will be applied.
*
* If the number itself has a country code of zero or an otherwise invalid
* country code, then we return the number with no formatting applied.
*
* Note this function takes care of the case for calling inside of NANPA and
* between Russia and Kazakhstan (who share the same country code). In those
* cases, no international prefix is used. For countries which have multiple
* international prefixes, the number in its INTERNATIONAL format will be
* returned instead.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number to be
* formatted.
* @param {string} countryCallingFrom the ISO 3166-1 two-letter country code
* that denotes the foreign country where the call is being placed.
* @return {string} the formatted phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatOutOfCountryCallingNumber =
function(number, countryCallingFrom) {
if (!this.isValidRegionCode_(countryCallingFrom)) {
return this.format(number,
i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL);
}
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
/** @type {string} */
var regionCode = this.getRegionCodeForCountryCode(countryCode);
/** @type {string} */
var nationalSignificantNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
if (!this.isValidRegionCode_(regionCode)) {
return nationalSignificantNumber;
}
if (countryCode == i18n.phonenumbers.PhoneNumberUtil.NANPA_COUNTRY_CODE_) {
if (this.isNANPACountry(countryCallingFrom)) {
// For NANPA countries, return the national format for these countries but
// prefix it with the country code.
return countryCode + ' ' +
this.format(number, i18n.phonenumbers.PhoneNumberFormat.NATIONAL);
}
} else if (countryCode == this.getCountryCodeForRegion(countryCallingFrom)) {
// For countries that share a country calling code, the country code need not
// be dialled. This also applies when dialling within a country, so this if
// clause covers both these cases. Technically this is the case for dialling
// from la Reunion to other overseas departments of France (French Guiana,
// Martinique, Guadeloupe), but not vice versa - so we don't cover this edge
// case for now and for those cases return the version including country code.
// Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion
return this.format(number,
i18n.phonenumbers.PhoneNumberFormat.NATIONAL);
}
/** @type {string} */
var formattedNationalNumber =
this.formatNationalNumber_(nationalSignificantNumber, regionCode,
i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL);
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(countryCallingFrom);
/** @type {string} */
var internationalPrefix = metadata.getInternationalPrefixOrDefault();
/** @type {string} */
var formattedExtension = this.maybeGetFormattedExtension_(number, regionCode);
// For countries that have multiple international prefixes, the international
// format of the number is returned, unless there is a preferred international
// prefix.
/** @type {string} */
var internationalPrefixForFormatting = '';
if (i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
i18n.phonenumbers.PhoneNumberUtil.UNIQUE_INTERNATIONAL_PREFIX_,
internationalPrefix)) {
internationalPrefixForFormatting = internationalPrefix;
} else if (metadata.hasPreferredInternationalPrefix()) {
internationalPrefixForFormatting =
metadata.getPreferredInternationalPrefixOrDefault();
}
return internationalPrefixForFormatting != '' ?
internationalPrefixForFormatting + ' ' + countryCode + ' ' +
formattedNationalNumber + formattedExtension :
this.formatNumberByFormat_(
countryCode, i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL,
formattedNationalNumber, formattedExtension);
};
/**
* Formats a phone number using the original phone number format that the number
* is parsed from. The original format is embedded in the country_code_source
* field of the PhoneNumber object passed in. If such information is missing,
* the number will be formatted into the NATIONAL format by default.
*
* @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that needs to
* be formatted in its original number format.
* @param {string} countryCallingFrom the country whose IDD needs to be prefixed
* if the original number has one.
* @return {string} the formatted phone number in its original number format.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatInOriginalFormat =
function(number, countryCallingFrom) {
if (!number.hasCountryCodeSource()) {
return this.format(number, i18n.phonenumbers.PhoneNumberFormat.NATIONAL);
}
switch (number.getCountryCodeSource()) {
case i18n.phonenumbers.PhoneNumber.CountryCodeSource
.FROM_NUMBER_WITH_PLUS_SIGN:
return this.format(number,
i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL);
case i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_IDD:
return this.formatOutOfCountryCallingNumber(number, countryCallingFrom);
case i18n.phonenumbers.PhoneNumber.CountryCodeSource
.FROM_NUMBER_WITHOUT_PLUS_SIGN:
return this.format(number,
i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL).substring(1);
case i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY:
default:
return this.format(number, i18n.phonenumbers.PhoneNumberFormat.NATIONAL);
}
};
/**
* Gets the national significant number of the a phone number. Note a national
* significant number doesn't contain a national prefix or any formatting.
*
* @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for
* which the national significant number is needed.
* @return {string} the national significant number of the PhoneNumber object
* passed in.
*/
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber =
function(number) {
// The leading zero in the national (significant) number of an Italian phone
// number has a special meaning. Unlike the rest of the world, it indicates
// the number is a landline number. There have been plans to migrate landline
// numbers to start with the digit two since December 2000, but it has not yet
// happened. See http://en.wikipedia.org/wiki/%2B39 for more details.
// Other countries such as Cote d'Ivoire and Gabon use this for their mobile
// numbers.
/** @type {string} */
var nationalNumber = '' + number.getNationalNumber();
if (number.hasItalianLeadingZero() && number.getItalianLeadingZero() &&
i18n.phonenumbers.PhoneNumberUtil.isLeadingZeroCountry(
number.getCountryCodeOrDefault())) {
return '0' + nationalNumber;
}
return nationalNumber;
};
/**
* A helper function that is used by format and formatByPattern.
*
* @param {number} countryCode the country calling code.
* @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
* phone number should be formatted into.
* @param {string} formattedNationalNumber
* @param {string} formattedExtension
* @return {string} the formatted phone number.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatNumberByFormat_ =
function(countryCode, numberFormat,
formattedNationalNumber, formattedExtension) {
switch (numberFormat) {
case i18n.phonenumbers.PhoneNumberFormat.E164:
return i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + countryCode +
formattedNationalNumber + formattedExtension;
case i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL:
return i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + countryCode + ' ' +
formattedNationalNumber + formattedExtension;
case i18n.phonenumbers.PhoneNumberFormat.NATIONAL:
default:
return formattedNationalNumber + formattedExtension;
}
};
/**
* Note in some countries, the national number can be written in two completely
* different ways depending on whether it forms part of the NATIONAL format or
* INTERNATIONAL format. The numberFormat parameter here is used to specify
* which format to use for those cases. If a carrierCode is specified, this will
* be inserted into the formatted string to replace $CC.
*
* @param {string} number a string of characters representing a phone number.
* @param {string} regionCode the ISO 3166-1 two-letter country code.
* @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
* phone number should be formatted into.
* @param {string=} opt_carrierCode
* @return {string} the formatted phone number.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatNationalNumber_ =
function(number, regionCode, numberFormat, opt_carrierCode) {
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
/** @type {Array.<i18n.phonenumbers.NumberFormat>} */
var intlNumberFormats = metadata.intlNumberFormatArray();
// When the intlNumberFormats exists, we use that to format national number
// for the INTERNATIONAL format instead of using the numberDesc.numberFormats.
/** @type {Array.<i18n.phonenumbers.NumberFormat>} */
var availableFormats =
(intlNumberFormats.length == 0 ||
numberFormat == i18n.phonenumbers.PhoneNumberFormat.NATIONAL) ?
metadata.numberFormatArray() : metadata.intlNumberFormatArray();
return this.formatAccordingToFormats_(number, availableFormats, numberFormat,
opt_carrierCode);
};
/**
* Note that carrierCode is optional - if NULL or an empty string, no carrier
* code replacement will take place. Carrier code replacement occurs before
* national prefix replacement.
*
* @param {string} nationalNumber a string of characters representing a phone
* number.
* @param {Array.<i18n.phonenumbers.NumberFormat>} availableFormats the
* available formats the phone number could be formatted into.
* @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the
* phone number should be formatted into.
* @param {string=} opt_carrierCode
* @return {string} the formatted phone number.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatAccordingToFormats_ =
function(nationalNumber, availableFormats, numberFormat, opt_carrierCode) {
/** @type {i18n.phonenumbers.NumberFormat} */
var numFormat;
/** @type {number} */
var l = availableFormats.length;
for (var i = 0; i < l; ++i) {
numFormat = availableFormats[i];
/** @type {number} */
var size = numFormat.leadingDigitsPatternCount();
if (size == 0 ||
// We always use the last leading_digits_pattern, as it is the most
// detailed.
nationalNumber
.search(numFormat.getLeadingDigitsPattern(size - 1)) == 0) {
/** @type {RegExp} */
var patternToMatch = new RegExp(numFormat.getPattern());
/** @type {string} */
var numberFormatRule = numFormat.getFormatOrDefault();
if (i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(patternToMatch,
nationalNumber)) {
if (opt_carrierCode != null && opt_carrierCode.length > 0) {
/** @type {string} */
var domesticCarrierCodeFormattingRule =
numFormat.getDomesticCarrierCodeFormattingRuleOrDefault();
if (domesticCarrierCodeFormattingRule.length > 0) {
// Replace the $CC in the formatting rule with the desired carrier
// code.
/** @type {string} */
var carrierCodeFormattingRule = domesticCarrierCodeFormattingRule
.replace(i18n.phonenumbers.PhoneNumberUtil.CC_PATTERN_,
opt_carrierCode);
// Now replace the $FG in the formatting rule with the first group
// and the carrier code combined in the appropriate way.
numberFormatRule = numberFormatRule.replace(
i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_PATTERN_,
carrierCodeFormattingRule);
}
}
/** @type {string} */
var nationalPrefixFormattingRule =
numFormat.getNationalPrefixFormattingRuleOrDefault();
if (numberFormat == i18n.phonenumbers.PhoneNumberFormat.NATIONAL &&
nationalPrefixFormattingRule != null &&
nationalPrefixFormattingRule.length > 0) {
return nationalNumber.replace(patternToMatch, numberFormatRule
.replace(i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_PATTERN_,
nationalPrefixFormattingRule));
} else {
return nationalNumber.replace(patternToMatch, numberFormatRule);
}
}
}
}
// If no pattern above is matched, we format the number as a whole.
return nationalNumber;
};
/**
* Gets a valid number for the specified country.
*
* @param {string} regionCode the ISO 3166-1 two-letter country code that
* denotes the country for which an example number is needed.
* @return {i18n.phonenumbers.PhoneNumber} a valid fixed-line number for the
* specified country. Returns null when the metadata does not contain such
* information.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getExampleNumber =
function(regionCode) {
return this.getExampleNumberForType(regionCode,
i18n.phonenumbers.PhoneNumberType.FIXED_LINE);
};
/**
* Gets a valid number, if any, for the specified country and number type.
*
* @param {string} regionCode the ISO 3166-1 two-letter country code that
* denotes the country for which an example number is needed.
* @param {i18n.phonenumbers.PhoneNumberType} type the type of number that is
* needed.
* @return {i18n.phonenumbers.PhoneNumber} a valid number for the specified
* country and type. Returns null when the metadata does not contain such
* information.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getExampleNumberForType =
function(regionCode, type) {
/** @type {i18n.phonenumbers.PhoneNumberDesc} */
var desc = this.getNumberDescByType_(
this.getMetadataForRegion(regionCode), type);
try {
if (desc.hasExampleNumber()) {
return this.parse(desc.getExampleNumberOrDefault(), regionCode);
}
} catch (e) {
}
return null;
};
/**
* Gets the formatted extension of a phone number, if the phone number had an
* extension specified. If not, it returns an empty string.
*
* @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have
* an extension.
* @param {string} regionCode the ISO 3166-1 two-letter country code.
* @return {string} the formatted extension if any.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.maybeGetFormattedExtension_ =
function(number, regionCode) {
if (!number.hasExtension()) {
return '';
} else {
return this.formatExtension_(number.getExtensionOrDefault(), regionCode);
}
};
/**
* Formats the extension part of the phone number by prefixing it with the
* appropriate extension prefix. This will be the default extension prefix,
* unless overridden by a preferred extension prefix for this country.
*
* @param {string} extensionDigits the extension digits.
* @param {string} regionCode the ISO 3166-1 two-letter country code.
* @return {string} the formatted extension.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.formatExtension_ =
function(extensionDigits, regionCode) {
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
if (metadata.hasPreferredExtnPrefix()) {
return metadata.getPreferredExtnPrefix() + extensionDigits;
} else {
return i18n.phonenumbers.PhoneNumberUtil.DEFAULT_EXTN_PREFIX_ +
extensionDigits;
}
};
/**
* @param {i18n.phonenumbers.PhoneMetadata} metadata
* @param {i18n.phonenumbers.PhoneNumberType} type
* @return {i18n.phonenumbers.PhoneNumberDesc}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getNumberDescByType_ =
function(metadata, type) {
switch (type) {
case i18n.phonenumbers.PhoneNumberType.PREMIUM_RATE:
return metadata.getPremiumRate();
case i18n.phonenumbers.PhoneNumberType.TOLL_FREE:
return metadata.getTollFree();
case i18n.phonenumbers.PhoneNumberType.MOBILE:
return metadata.getMobile();
case i18n.phonenumbers.PhoneNumberType.FIXED_LINE:
case i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE:
return metadata.getFixedLine();
case i18n.phonenumbers.PhoneNumberType.SHARED_COST:
return metadata.getSharedCost();
case i18n.phonenumbers.PhoneNumberType.VOIP:
return metadata.getVoip();
case i18n.phonenumbers.PhoneNumberType.PERSONAL_NUMBER:
return metadata.getPersonalNumber();
case i18n.phonenumbers.PhoneNumberType.PAGER:
return metadata.getPager();
default:
return metadata.getGeneralDesc();
}
};
/**
* Gets the type of a phone number.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
* to know the type.
* @return {i18n.phonenumbers.PhoneNumberType} the type of the phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getNumberType =
function(number) {
/** @type {string} */
var regionCode = /** @type {string} */ (this.getRegionCodeForNumber(number));
if (!this.isValidRegionCode_(regionCode)) {
return i18n.phonenumbers.PhoneNumberType.UNKNOWN;
}
/** @type {string} */
var nationalSignificantNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
return this.getNumberTypeHelper_(nationalSignificantNumber,
this.getMetadataForRegion(regionCode));
};
/**
* @param {string} nationalNumber
* @param {i18n.phonenumbers.PhoneMetadata} metadata
* @return {i18n.phonenumbers.PhoneNumberType}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getNumberTypeHelper_ =
function(nationalNumber, metadata) {
/** @type {i18n.phonenumbers.PhoneNumberDesc} */
var generalNumberDesc = metadata.getGeneralDesc();
if (!generalNumberDesc.hasNationalNumberPattern() ||
!this.isNumberMatchingDesc_(nationalNumber, generalNumberDesc)) {
return i18n.phonenumbers.PhoneNumberType.UNKNOWN;
}
if (this.isNumberMatchingDesc_(nationalNumber, metadata.getPremiumRate())) {
return i18n.phonenumbers.PhoneNumberType.PREMIUM_RATE;
}
if (this.isNumberMatchingDesc_(nationalNumber, metadata.getTollFree())) {
return i18n.phonenumbers.PhoneNumberType.TOLL_FREE;
}
if (this.isNumberMatchingDesc_(nationalNumber, metadata.getSharedCost())) {
return i18n.phonenumbers.PhoneNumberType.SHARED_COST;
}
if (this.isNumberMatchingDesc_(nationalNumber, metadata.getVoip())) {
return i18n.phonenumbers.PhoneNumberType.VOIP;
}
if (this.isNumberMatchingDesc_(nationalNumber,
metadata.getPersonalNumber())) {
return i18n.phonenumbers.PhoneNumberType.PERSONAL_NUMBER;
}
if (this.isNumberMatchingDesc_(nationalNumber,
metadata.getPager())) {
return i18n.phonenumbers.PhoneNumberType.PAGER;
}
/** @type {boolean} */
var isFixedLine = this.isNumberMatchingDesc_(nationalNumber, metadata
.getFixedLine());
if (isFixedLine) {
if (metadata.getSameMobileAndFixedLinePattern()) {
return i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE;
} else if (this.isNumberMatchingDesc_(nationalNumber,
metadata.getMobile())) {
return i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE;
}
return i18n.phonenumbers.PhoneNumberType.FIXED_LINE;
}
// Otherwise, test to see if the number is mobile. Only do this if certain
// that the patterns for mobile and fixed line aren't the same.
if (!metadata.getSameMobileAndFixedLinePattern() &&
this.isNumberMatchingDesc_(nationalNumber, metadata.getMobile())) {
return i18n.phonenumbers.PhoneNumberType.MOBILE;
}
return i18n.phonenumbers.PhoneNumberType.UNKNOWN;
};
/**
* @param {?string} regionCode
* @return {i18n.phonenumbers.PhoneMetadata}
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getMetadataForRegion =
function(regionCode) {
if (regionCode == null) {
return null;
}
regionCode = regionCode.toUpperCase();
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.countryToMetadata[regionCode];
if (metadata == null) {
/** @type {goog.proto2.PbLiteSerializer} */
var serializer = new goog.proto2.PbLiteSerializer();
/** @type {Array} */
var metadataSerialized =
i18n.phonenumbers.metadata.countryToMetadata[regionCode];
if (metadataSerialized == null) {
return null;
}
metadata = /** @type {i18n.phonenumbers.PhoneMetadata} */ (
serializer.deserialize(i18n.phonenumbers.PhoneMetadata.getDescriptor(),
metadataSerialized));
this.countryToMetadata[regionCode] = metadata;
}
return metadata;
};
/**
* @param {string} nationalNumber
* @param {i18n.phonenumbers.PhoneNumberDesc} numberDesc
* @return {boolean}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isNumberMatchingDesc_ =
function(nationalNumber, numberDesc) {
return i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
numberDesc.getPossibleNumberPattern(), nationalNumber) &&
i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
numberDesc.getNationalNumberPattern(), nationalNumber);
};
/**
* Tests whether a phone number matches a valid pattern. Note this doesn't
* verify the number is actually in use, which is impossible to tell by just
* looking at a number itself.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
* to validate.
* @return {boolean} a boolean that indicates whether the number is of a valid
* pattern.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isValidNumber = function(number) {
/** @type {string} */
var regionCode = /** @type {string} */ (this.getRegionCodeForNumber(number));
return this.isValidRegionCode_(regionCode) &&
this.isValidNumberForRegion(number, regionCode);
};
/**
* Tests whether a phone number is valid for a certain region. Note this doesn't
* verify the number is actually in use, which is impossible to tell by just
* looking at a number itself. If the country code is not the same as the
* country code for the region, this immediately exits with false. After this,
* the specific number pattern rules for the region are examined. This is useful
* for determining for example whether a particular number is valid for Canada,
* rather than just a valid NANPA number.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number that we want
* to validate.
* @param {string} regionCode the ISO 3166-1 two-letter country code that
* denotes the region/country that we want to validate the phone number for.
* @return {boolean} a boolean that indicates whether the number is of a valid
* pattern.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isValidNumberForRegion =
function(number, regionCode) {
if (number.getCountryCodeOrDefault() !=
this.getCountryCodeForRegion(regionCode)) {
return false;
}
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
/** @type {i18n.phonenumbers.PhoneNumberDesc} */
var generalNumDesc = metadata.getGeneralDesc();
/** @type {string} */
var nationalSignificantNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
// For countries where we don't have metadata for PhoneNumberDesc, we treat
// any number passed in as a valid number if its national significant number
// is between the minimum and maximum lengths defined by ITU for a national
// significant number.
if (!generalNumDesc.hasNationalNumberPattern()) {
/** @type {number} */
var numberLength = nationalSignificantNumber.length;
return numberLength >
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_ &&
numberLength <= i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_FOR_NSN_;
}
return this.getNumberTypeHelper_(nationalSignificantNumber, metadata) !=
i18n.phonenumbers.PhoneNumberType.UNKNOWN;
};
/**
* Returns the country/region where a phone number is from. This could be used
* for geo-coding in the country/region level.
*
* @param {i18n.phonenumbers.PhoneNumber} number the phone number whose origin
* we want to know.
* @return {?string} the country/region where the phone number is from, or null
* if no country matches this calling code.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getRegionCodeForNumber =
function(number) {
if (number == null) {
return null;
}
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
/** @type {Array.<string>} */
var regions =
i18n.phonenumbers.metadata.countryCodeToRegionCodeMap[countryCode];
if (regions == null) {
return null;
}
if (regions.length == 1) {
return regions[0];
} else {
return this.getRegionCodeForNumberFromRegionList_(number, regions);
}
};
/**
* @param {i18n.phonenumbers.PhoneNumber} number
* @param {Array.<string>} regionCodes
* @return {?string}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.
getRegionCodeForNumberFromRegionList_ = function(number, regionCodes) {
/** @type {string} */
var nationalNumber = '' + number.getNationalNumber();
/** @type {string} */
var regionCode;
/** @type {number} */
var regionCodesLength = regionCodes.length;
for (var i = 0; i < regionCodesLength; i++) {
regionCode = regionCodes[i];
// If leadingDigits is present, use this. Otherwise, do full validation.
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
if (metadata.hasLeadingDigits()) {
if (nationalNumber.search(metadata.getLeadingDigits()) == 0) {
return regionCode;
}
} else if (this.getNumberTypeHelper_(nationalNumber, metadata) !=
i18n.phonenumbers.PhoneNumberType.UNKNOWN) {
return regionCode;
}
}
return null;
};
/**
* Returns the region code that matches the specific country code. In the case
* of no region code being found, ZZ will be returned. In the case of multiple
* regions, the one designated in the metadata as the "main" country for this
* calling code will be returned.
*
* @param {number} countryCode the country calling code.
* @return {string}
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getRegionCodeForCountryCode =
function(countryCode) {
/** @type {Array.<string>} */
var regionCodes =
i18n.phonenumbers.metadata.countryCodeToRegionCodeMap[countryCode];
return regionCodes == null ? 'ZZ' : regionCodes[0];
};
/**
* Returns the country calling code for a specific region. For example, this
* would be 1 for the United States, and 64 for New Zealand.
*
* @param {?string} regionCode the ISO 3166-1 two-letter country code that
* denotes the country/region that we want to get the country code for.
* @return {number} the country calling code for the country/region denoted by
* regionCode.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getCountryCodeForRegion =
function(regionCode) {
if (!this.isValidRegionCode_(regionCode)) {
return 0;
}
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
if (metadata == null) {
return 0;
}
return metadata.getCountryCodeOrDefault();
};
/**
* Returns the national dialling prefix for a specific region. For example, this
* would be 1 for the United States, and 0 for New Zealand. Set stripNonDigits
* to true to strip symbols like "~" (which indicates a wait for a dialling
* tone) from the prefix returned. If no national prefix is present, we return
* null.
*
* Warning: Do not use this method for do-your-own formatting - for some
* countries, the national dialling prefix is used only for certain types of
* numbers. Use the library's formatting functions to prefix the national prefix
* when required.
*
* @param {string} regionCode the ISO 3166-1 two-letter country code that
* denotes the country/region that we want to get the dialling prefix for.
* @param {boolean} stripNonDigits true to strip non-digits from the national
* dialling prefix.
* @return {?string} the dialling prefix for the country/region denoted by
* regionCode.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.getNddPrefixForRegion = function(
regionCode, stripNonDigits) {
if (!this.isValidRegionCode_(regionCode)) {
return null;
}
/** @type {i18n.phonenumbers.PhoneMetadata} */
var metadata = this.getMetadataForRegion(regionCode);
if (metadata == null) {
return null;
}
/** @type {string} */
var nationalPrefix = metadata.getNationalPrefixOrDefault();
// If no national prefix was found, we return null.
if (nationalPrefix.length == 0) {
return null;
}
if (stripNonDigits) {
// Note: if any other non-numeric symbols are ever used in national
// prefixes, these would have to be removed here as well.
nationalPrefix = nationalPrefix.replace('~', '');
}
return nationalPrefix;
};
/**
* Check if a country is one of the countries under the North American Numbering
* Plan Administration (NANPA).
*
* @param {string} regionCode the ISO 3166-1 two-letter country code.
* @return {boolean} true if regionCode is one of the countries under NANPA.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isNANPACountry =
function(regionCode) {
return goog.array.contains(
i18n.phonenumbers.metadata.countryCodeToRegionCodeMap[
i18n.phonenumbers.PhoneNumberUtil.NANPA_COUNTRY_CODE_],
regionCode.toUpperCase());
};
/**
* Check whether countryCode represents the country calling code from a country
* whose national significant number could contain a leading zero. An example of
* such a country is Italy.
*
* @param {number} countryCode the country calling code.
* @return {boolean}
*/
i18n.phonenumbers.PhoneNumberUtil.isLeadingZeroCountry = function(countryCode) {
return countryCode in
i18n.phonenumbers.PhoneNumberUtil.LEADING_ZERO_COUNTRIES_;
};
/**
* Convenience wrapper around isPossibleNumberWithReason. Instead of returning
* the reason for failure, this method returns a boolean value.
*
* @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
* checked.
* @return {boolean} true if the number is possible.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isPossibleNumber =
function(number) {
return this.isPossibleNumberWithReason(number) ==
i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE;
};
/**
* Check whether a phone number is a possible number. It provides a more lenient
* check than isValidNumber in the following sense:
*
* 1. It only checks the length of phone numbers. In particular, it doesn't
* check starting digits of the number.
*
* 2. It doesn't attempt to figure out the type of the number, but uses general
* rules which applies to all types of phone numbers in a country. Therefore, it
* is much faster than isValidNumber.
*
* 3. For fixed line numbers, many countries have the concept of area code,
* which together with subscriber number constitute the national significant
* number. It is sometimes okay to dial the subscriber number only when dialing
* in the same area. This function will return true if the
* subscriber-number-only version is passed in. On the other hand, because
* isValidNumber validates using information on both starting digits (for fixed
* line numbers, that would most likely be area codes) and length (obviously
* includes the length of area codes for fixed line numbers), it will return
* false for the subscriber-number-only version.
*
* @param {i18n.phonenumbers.PhoneNumber} number the number that needs to be
* checked.
* @return {i18n.phonenumbers.PhoneNumberUtil.ValidationResult} a
* ValidationResult object which indicates whether the number is possible.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isPossibleNumberWithReason =
function(number) {
/** @type {number} */
var countryCode = number.getCountryCodeOrDefault();
// Note: For Russian Fed and NANPA numbers, we just use the rules from the
// default region (US or Russia) since the getRegionCodeForNumber will not
// work if the number is possible but not valid. This would need to be
// revisited if the possible number pattern ever differed between various
// countries within those plans.
/** @type {string} */
var regionCode = this.getRegionCodeForCountryCode(countryCode);
if (!this.isValidRegionCode_(regionCode)) {
return i18n.phonenumbers.PhoneNumberUtil.ValidationResult
.INVALID_COUNTRY_CODE;
}
/** @type {string} */
var nationalNumber =
i18n.phonenumbers.PhoneNumberUtil.getNationalSignificantNumber(number);
/** @type {i18n.phonenumbers.PhoneNumberDesc} */
var generalNumDesc = this.getMetadataForRegion(regionCode).getGeneralDesc();
// Handling case of numbers with no metadata.
if (!generalNumDesc.hasNationalNumberPattern()) {
/** @type {number} */
var numberLength = nationalNumber.length;
if (numberLength < i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_) {
return i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_SHORT;
} else if (numberLength >
i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_FOR_NSN_) {
return i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_LONG;
} else {
return i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE;
}
}
/** @type {string} */
var possibleNumberPattern =
generalNumDesc.getPossibleNumberPatternOrDefault();
/** @type {Array.<string> } */
var matchedGroups = nationalNumber.match('^' + possibleNumberPattern);
/** @type {string} */
var firstGroup = matchedGroups ? matchedGroups[0] : '';
if (firstGroup.length > 0) {
return (firstGroup.length == nationalNumber.length) ?
i18n.phonenumbers.PhoneNumberUtil.ValidationResult.IS_POSSIBLE :
i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_LONG;
} else {
return i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_SHORT;
}
};
/**
* Check whether a phone number is a possible number given a number in the form
* of a string, and the country where the number could be dialed from. It
* provides a more lenient check than isValidNumber. See
* isPossibleNumber(number) for details.
*
* This method first parses the number, then invokes
* isPossibleNumber(PhoneNumber number) with the resultant PhoneNumber object.
*
* @param {string} number the number that needs to be checked, in the form of a
* string.
* @param {string} countryDialingFrom the ISO 3166-1 two-letter country code
* that denotes the country that we are expecting the number to be dialed
* from. Note this is different from the country where the number brlongs.
* For example, the number +1 650 253 0000 is a number that belongs to US.
* When written in this form, it could be dialed from any country. When it
* is written as 00 1 650 253 0000, it could be dialed from any country
* which has international prefix 00. When it is written as
* 650 253 0000, it could only be dialed from US, and when written as
* 253 0000, it could only be dialed from US (Mountain View, CA, to be more
* specific).
* @return {boolean} true if the number is possible.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isPossibleNumberString =
function(number, countryDialingFrom) {
try {
return this.isPossibleNumber(this.parse(number, countryDialingFrom));
} catch (e) {
return false;
}
};
/**
* Attempts to extract a valid number from a phone number that is too long to be
* valid, and resets the PhoneNumber object passed in to that valid version. If
* no valid number could be extracted, the PhoneNumber object passed in will not
* be modified.
* @param {i18n.phonenumbers.PhoneNumber} number a PhoneNumber object which
* contains a number that is too long to be valid.
* @return {boolean} true if a valid phone number can be successfully extracted.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.truncateTooLongNumber =
function(number) {
if (this.isValidNumber(number)) {
return true;
}
/** @type {i18n.phonenumbers.PhoneNumber} */
var numberCopy = new i18n.phonenumbers.PhoneNumber();
numberCopy.mergeFrom(number);
/** @type {number} */
var nationalNumber = number.getNationalNumberOrDefault();
do {
nationalNumber = Math.floor(nationalNumber / 10);
numberCopy.setNationalNumber(nationalNumber);
if (nationalNumber == 0 ||
this.isPossibleNumberWithReason(numberCopy) ==
i18n.phonenumbers.PhoneNumberUtil.ValidationResult.TOO_SHORT) {
return false;
}
} while (!this.isValidNumber(numberCopy));
number.setNationalNumber(nationalNumber);
return true;
};
/**
* Extracts country code from fullNumber, returns it and places the remaining
* number in nationalNumber. It assumes that the leading plus sign or IDD has
* already been removed. Returns 0 if fullNumber doesn't start with a valid
* country code, and leaves nationalNumber unmodified.
*
* @param {!goog.string.StringBuffer} fullNumber
* @param {!goog.string.StringBuffer} nationalNumber
* @return {number}
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.extractCountryCode =
function(fullNumber, nationalNumber) {
/** @type {string} */
var fullNumberStr = fullNumber.toString();
/** @type {number} */
var potentialCountryCode;
/** @type {number} */
var numberLength = fullNumberStr.length;
for (var i = 1; i <= 3 && i <= numberLength; ++i) {
potentialCountryCode = parseInt(fullNumberStr.substring(0, i), 10);
if (potentialCountryCode in
i18n.phonenumbers.metadata.countryCodeToRegionCodeMap) {
nationalNumber.append(fullNumberStr.substring(i));
return potentialCountryCode;
}
}
return 0;
};
/**
* Tries to extract a country code from a number. This method will return zero
* if no country code is considered to be present. Country codes are extracted
* in the following ways:
* - by stripping the international dialing prefix of the country the person is
* dialing from, if this is present in the number, and looking at the next
* digits
* - by stripping the '+' sign if present and then looking at the next digits
* - by comparing the start of the number and the country code of the default
* region. If the number is not considered possible for the numbering plan of
* the default region initially, but starts with the country code of this
* region, validation will be reattempted after stripping this country code. If
* this number is considered a possible number, then the first digits will be
* considered the country code and removed as such.
*
* It will throw a i18n.phonenumbers.Error if the number starts with a '+' but
* the country code supplied after this does not match that of any known
* country.
*
* @param {string} number non-normalized telephone number that we wish to
* extract a country code from - may begin with '+'.
* @param {i18n.phonenumbers.PhoneMetadata} defaultRegionMetadata metadata
* about the region this number may be from.
* @param {!goog.string.StringBuffer} nationalNumber a string buffer to store
* the national significant number in, in the case that a country code was
* extracted. The number is appended to any existing contents. If no country
* code was extracted, this will be left unchanged.
* @param {boolean} storeCountryCodeSource true if the country_code_source field
* of phoneNumber should be populated.
* @param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object
* that needs to be populated with country code and country code source.
* Note the country code is always populated, whereas country code source is
* only populated when keepCountryCodeSource is true.
* @return {number} the country code extracted or 0 if none could be extracted.
* @throws {i18n.phonenumbers.Error}
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.maybeExtractCountryCode =
function(number, defaultRegionMetadata, nationalNumber,
storeCountryCodeSource, phoneNumber) {
if (number.length == 0) {
return 0;
}
/** @type {!goog.string.StringBuffer} */
var fullNumber = new goog.string.StringBuffer(number);
// Set the default prefix to be something that will never match.
/** @type {?string} */
var possibleCountryIddPrefix;
if (defaultRegionMetadata != null) {
possibleCountryIddPrefix = defaultRegionMetadata.getInternationalPrefix();
}
if (possibleCountryIddPrefix == null) {
possibleCountryIddPrefix = 'NonMatch';
}
/** @type {i18n.phonenumbers.PhoneNumber.CountryCodeSource} */
var countryCodeSource = this.maybeStripInternationalPrefixAndNormalize(
fullNumber, possibleCountryIddPrefix);
if (storeCountryCodeSource) {
phoneNumber.setCountryCodeSource(countryCodeSource);
}
if (countryCodeSource !=
i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY) {
if (fullNumber.getLength() <
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_) {
throw i18n.phonenumbers.Error.TOO_SHORT_AFTER_IDD;
}
/** @type {number} */
var potentialCountryCode = this.extractCountryCode(fullNumber,
nationalNumber);
if (potentialCountryCode != 0) {
phoneNumber.setCountryCode(potentialCountryCode);
return potentialCountryCode;
}
// If this fails, they must be using a strange country code that we don't
// recognize, or that doesn't exist.
throw i18n.phonenumbers.Error.INVALID_COUNTRY_CODE;
} else if (defaultRegionMetadata != null) {
// Check to see if the number is valid for the default region already. If
// not, we check to see if the country code for the default region is
// present at the start of the number.
/** @type {i18n.phonenumbers.PhoneNumberDesc} */
var generalDesc = defaultRegionMetadata.getGeneralDesc();
/** @type {RegExp} */
var validNumberPattern = new RegExp(generalDesc.getNationalNumberPattern());
if (!i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
validNumberPattern, fullNumber.toString())) {
/** @type {number} */
var defaultCountryCode = defaultRegionMetadata.getCountryCodeOrDefault();
/** @type {string} */
var defaultCountryCodeString = '' + defaultCountryCode;
/** @type {string} */
var normalizedNumber = fullNumber.toString();
if (goog.string.startsWith(normalizedNumber, defaultCountryCodeString)) {
// If so, strip this, and see if the resultant number is valid.
/** @type {!goog.string.StringBuffer} */
var potentialNationalNumber = new goog.string.StringBuffer(
normalizedNumber.substring(defaultCountryCodeString.length));
this.maybeStripNationalPrefix(potentialNationalNumber,
defaultRegionMetadata.getNationalPrefixForParsing(),
defaultRegionMetadata.getNationalPrefixTransformRule(),
validNumberPattern);
/** @type {string} */
var potentialNationalNumberStr = potentialNationalNumber.toString();
/** @type {Array.<string> } */
var matchedGroups = potentialNationalNumberStr.match(
'^' + generalDesc.getPossibleNumberPattern());
/** @type {number} */
var possibleNumberMatchedLength = matchedGroups &&
matchedGroups[0] != null && matchedGroups[0].length || 0;
// If the resultant number is either valid, or still too long even with
// the country code stripped, we consider this a better result and keep
// the potential national number.
if (i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(
validNumberPattern, potentialNationalNumberStr) ||
possibleNumberMatchedLength > 0 &&
possibleNumberMatchedLength != potentialNationalNumberStr.length) {
nationalNumber.append(potentialNationalNumberStr);
if (storeCountryCodeSource) {
phoneNumber.setCountryCodeSource(
i18n.phonenumbers.PhoneNumber.CountryCodeSource
.FROM_NUMBER_WITHOUT_PLUS_SIGN);
}
phoneNumber.setCountryCode(defaultCountryCode);
return defaultCountryCode;
}
}
}
}
// No country code present.
phoneNumber.setCountryCode(0);
return 0;
};
/**
* Strips the IDD from the start of the number if present. Helper function used
* by maybeStripInternationalPrefixAndNormalize.
*
* @param {RegExp} iddPattern the regular expression for the international
* prefix.
* @param {!goog.string.StringBuffer} number the phone number that we wish to
* strip any international dialing prefix from.
* @return {boolean} true if an international prefix was present.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parsePrefixAsIdd_ =
function(iddPattern, number) {
/** @type {string} */
var numberStr = number.toString();
if (numberStr.search(iddPattern) == 0) {
/** @type {number} */
var matchEnd = numberStr.match(iddPattern)[0].length;
/** @type {Array.<string> } */
var matchedGroups = numberStr.substring(matchEnd).match(
i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN_);
if (matchedGroups && matchedGroups[1] != null &&
matchedGroups[1].length > 0) {
/** @type {string} */
var normalizedGroup = i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_(
matchedGroups[1], i18n.phonenumbers.PhoneNumberUtil.DIGIT_MAPPINGS,
true);
if (normalizedGroup == '0') {
return false;
}
}
number.clear();
number.append(numberStr.substring(matchEnd));
return true;
}
return false;
};
/**
* Strips any international prefix (such as +, 00, 011) present in the number
* provided, normalizes the resulting number, and indicates if an international
* prefix was present.
*
* @param {!goog.string.StringBuffer} number the non-normalized telephone number
* that we wish to strip any international dialing prefix from.
* @param {string} possibleIddPrefix the international direct dialing prefix
* from the country we think this number may be dialed in.
* @return {i18n.phonenumbers.PhoneNumber.CountryCodeSource} the corresponding
* CountryCodeSource if an international dialing prefix could be removed
* from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if
* the number did not seem to be in international format.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.
maybeStripInternationalPrefixAndNormalize = function(number,
possibleIddPrefix) {
/** @type {string} */
var numberStr = number.toString();
if (numberStr.length == 0) {
return i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY;
}
// Check to see if the number begins with one or more plus signs.
if (i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN_.test(numberStr)) {
numberStr = numberStr.replace(
i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN_, '');
// Can now normalize the rest of the number since we've consumed the "+"
// sign at the start.
number.clear();
number.append(i18n.phonenumbers.PhoneNumberUtil.normalize(numberStr));
return i18n.phonenumbers.PhoneNumber.CountryCodeSource
.FROM_NUMBER_WITH_PLUS_SIGN;
}
// Attempt to parse the first digits as an international prefix.
/** @type {RegExp} */
var iddPattern = new RegExp(possibleIddPrefix);
if (this.parsePrefixAsIdd_(iddPattern, number)) {
i18n.phonenumbers.PhoneNumberUtil.normalizeSB_(number);
return i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_IDD;
}
// If still not found, then try and normalize the number and then try again.
// This shouldn't be done before, since non-numeric characters (+ and ~) may
// legally be in the international prefix.
i18n.phonenumbers.PhoneNumberUtil.normalizeSB_(number);
return this.parsePrefixAsIdd_(iddPattern, number) ?
i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_IDD :
i18n.phonenumbers.PhoneNumber.CountryCodeSource.FROM_DEFAULT_COUNTRY;
};
/**
* Strips any national prefix (such as 0, 1) present in the number provided.
*
* @param {!goog.string.StringBuffer} number the normalized telephone number
* that we wish to strip any national dialing prefix from.
* @param {?string} possibleNationalPrefix a regex that represents the national
* direct dialing prefix from the country we think this number may be dialed
* in.
* @param {?string} transformRule the string that specifies how number should be
* transformed according to the regex specified in possibleNationalPrefix.
* @param {RegExp} nationalNumberRule a regular expression that specifies what a
* valid phonenumber from this region should look like after any national
* prefix was stripped or transformed.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.maybeStripNationalPrefix =
function(number, possibleNationalPrefix, transformRule, nationalNumberRule) {
/** @type {string} */
var numberStr = number.toString();
/** @type {number} */
var numberLength = numberStr.length;
if (numberLength == 0 || possibleNationalPrefix == null ||
possibleNationalPrefix.length == 0) {
// Early return for numbers of zero length.
return;
}
// Attempt to parse the first digits as a national prefix.
/** @type {RegExp} */
var re = new RegExp('^' + possibleNationalPrefix);
/** @type {Array.<string>} */
var m = re.exec(numberStr);
if (m) {
/** @type {string} */
var transformedNumber;
// m[1] == null implies nothing was captured by the capturing groups in
// possibleNationalPrefix; therefore, no transformation is necessary, and
// we just remove the national prefix.
if (transformRule == null || transformRule.length == 0 || m[1] == null ||
m[1].length == 0) {
transformedNumber = numberStr.substring(m[0].length);
} else {
transformedNumber = numberStr.replace(re, transformRule);
}
// Check that the resultant number is viable. If not, return.
if (!i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_(nationalNumberRule,
transformedNumber)) {
return;
}
number.clear();
number.append(transformedNumber);
}
};
/**
* Strips any extension (as in, the part of the number dialled after the call is
* connected, usually indicated with extn, ext, x or similar) from the end of
* the number, and returns it.
*
* @param {!goog.string.StringBuffer} number the non-normalized telephone number
* that we wish to strip the extension from.
* @return {string} the phone extension.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.maybeStripExtension =
function(number) {
/** @type {string} */
var numberStr = number.toString();
/** @type {number} */
var mStart =
numberStr.search(i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERN_);
// If we find a potential extension, and the number preceding this is a viable
// number, we assume it is an extension.
if (mStart >= 0 && i18n.phonenumbers.PhoneNumberUtil.isViablePhoneNumber(
numberStr.substring(0, mStart))) {
// The numbers are captured into groups in the regular expression.
/** @type {Array.<string>} */
var matchedGroups =
numberStr.match(i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERN_);
/** @type {number} */
var matchedGroupsLength = matchedGroups.length;
for (var i = 1; i < matchedGroupsLength; ++i) {
if (matchedGroups[i] != null && matchedGroups[i].length > 0) {
number.clear();
number.append(numberStr.substring(0, mStart));
return matchedGroups[i];
}
}
}
return '';
};
/**
* Parses a string and returns it in proto buffer format. This method will throw
* a i18n.phonenumbers.Error if the number is not considered to be a possible
* number. Note that validation of whether the number is actually a valid number
* for a particular country/region is not performed. This can be done separately
* with isValidNumber.
*
* @param {string} numberToParse number that we are attempting to parse. This
* can contain formatting such as +, ( and -, as well as a phone number
* extension.
* @param {?string} defaultCountry the ISO 3166-1 two-letter country code that
* denotes the country that we are expecting the number to be from. This is
* only used if the number being parsed is not written in international
* format. The country code for the number in this case would be stored as
* that of the default country supplied. If the number is guaranteed to
* start with a '+' followed by the country code, then 'ZZ' or null can be
* supplied.
* @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
* with the parsed number.
* @throws {i18n.phonenumbers.Error} if the string is not considered to be a
* viable phone number or if no default country was supplied and the number
* is not in international format (does not start with +).
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parse = function(numberToParse,
defaultCountry) {
if (!this.isValidRegionCode_(defaultCountry)) {
if (numberToParse.length > 0 && numberToParse.charAt(0) !=
i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
throw i18n.phonenumbers.Error.INVALID_COUNTRY_CODE;
}
}
return this.parseHelper_(numberToParse, defaultCountry, false);
};
/**
* Parses a string and returns it in proto buffer format. This method differs
* from parse() in that it always populates the raw_input field of the protocol
* buffer with numberToParse as well as the country_code_source field.
*
* @param {string} numberToParse number that we are attempting to parse. This
* can contain formatting such as +, ( and -, as well as a phone number
* extension.
* @param {?string} defaultCountry the ISO 3166-1 two-letter country code that
* denotes the country that we are expecting the number to be from. This is
* only used if the number being parsed is not written in international
* format. The country code for the number in this case would be stored as
* that of the default country supplied.
* @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
* with the parsed number.
* @throws {i18n.phonenumbers.Error} if the string is not considered to be a
* viable phone number or if no default country was supplied and the number
* is not in international format (does not start with +).
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parseAndKeepRawInput =
function(numberToParse, defaultCountry) {
if (!this.isValidRegionCode_(defaultCountry)) {
if (numberToParse.length > 0 && numberToParse.charAt(0) !=
i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) {
throw i18n.phonenumbers.Error.INVALID_COUNTRY_CODE;
}
}
return this.parseHelper_(numberToParse, defaultCountry, true);
};
/**
* Parses a string and returns it in proto buffer format. This method is the
* same as the public parse() method, with the exception that it allows the
* default country to be null, for use by isNumberMatch().
*
* @param {string} numberToParse number that we are attempting to parse. This
* can contain formatting such as +, ( and -, as well as a phone number
* extension.
* @param {?string} defaultCountry the ISO 3166-1 two-letter country code that
* denotes the country that we are expecting the number to be from. This is
* only used if the number being parsed is not written in international
* format. The country code for the number in this case would be stored as
* that of the default country supplied.
* @param {boolean} keepRawInput whether to populate the raw_input field of the
* phoneNumber with numberToParse.
* @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled
* with the parsed number.
* @throws {i18n.phonenumbers.Error}
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.parseHelper_ =
function(numberToParse, defaultCountry, keepRawInput) {
// Extract a possible number from the string passed in (this strips leading
// characters that could not be the start of a phone number.)
/** @type {string} */
var number =
i18n.phonenumbers.PhoneNumberUtil.extractPossibleNumber(numberToParse);
if (!i18n.phonenumbers.PhoneNumberUtil.isViablePhoneNumber(number)) {
throw i18n.phonenumbers.Error.NOT_A_NUMBER;
}
/** @type {i18n.phonenumbers.PhoneNumber} */
var phoneNumber = new i18n.phonenumbers.PhoneNumber();
if (keepRawInput) {
phoneNumber.setRawInput(numberToParse);
}
/** @type {!goog.string.StringBuffer} */
var nationalNumber = new goog.string.StringBuffer(number);
// Attempt to parse extension first, since it doesn't require
// country-specific data and we want to have the non-normalised number here.
/** @type {string} */
var extension = this.maybeStripExtension(nationalNumber);
if (extension.length > 0) {
phoneNumber.setExtension(extension);
}
/** @type {i18n.phonenumbers.PhoneMetadata} */
var countryMetadata = this.getMetadataForRegion(defaultCountry);
// Check to see if the number is given in international format so we know
// whether this number is from the default country or not.
/** @type {!goog.string.StringBuffer} */
var normalizedNationalNumber = new goog.string.StringBuffer();
/** @type {number} */
var countryCode = this.maybeExtractCountryCode(nationalNumber.toString(),
countryMetadata, normalizedNationalNumber, keepRawInput, phoneNumber);
if (countryCode != 0) {
/** @type {string} */
var phoneNumberRegion = this.getRegionCodeForCountryCode(countryCode);
if (phoneNumberRegion != defaultCountry) {
countryMetadata = this.getMetadataForRegion(phoneNumberRegion);
}
} else {
// If no extracted country code, use the region supplied instead. The
// national number is just the normalized version of the number we were
// given to parse.
i18n.phonenumbers.PhoneNumberUtil.normalizeSB_(nationalNumber);
normalizedNationalNumber.append(nationalNumber.toString());
if (defaultCountry != null) {
countryCode = countryMetadata.getCountryCodeOrDefault();
phoneNumber.setCountryCode(countryCode);
} else if (keepRawInput) {
phoneNumber.clearCountryCodeSource();
}
}
if (normalizedNationalNumber.getLength() <
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_) {
throw i18n.phonenumbers.Error.TOO_SHORT_NSN;
}
if (countryMetadata != null) {
/** @type {RegExp} */
var validNumberPattern =
new RegExp(countryMetadata.getGeneralDesc().getNationalNumberPattern());
this.maybeStripNationalPrefix(normalizedNationalNumber, countryMetadata
.getNationalPrefixForParsing(), countryMetadata
.getNationalPrefixTransformRule(), validNumberPattern);
}
/** @type {string} */
var normalizedNationalNumberStr = normalizedNationalNumber.toString();
/** @type {number} */
var lengthOfNationalNumber = normalizedNationalNumberStr.length;
if (lengthOfNationalNumber <
i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_) {
throw i18n.phonenumbers.Error.TOO_SHORT_NSN;
}
if (lengthOfNationalNumber >
i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_FOR_NSN_) {
throw i18n.phonenumbers.Error.TOO_LONG;
}
if (normalizedNationalNumberStr.charAt(0) == '0' &&
i18n.phonenumbers.PhoneNumberUtil.isLeadingZeroCountry(countryCode)) {
phoneNumber.setItalianLeadingZero(true);
}
phoneNumber.setNationalNumber(parseInt(normalizedNationalNumberStr, 10));
return phoneNumber;
};
/**
* Takes two phone numbers and compares them for equality.
*
* Returns EXACT_MATCH if the country code, NSN, presence of a leading zero for
* Italian numbers and any extension present are the same. Returns NSN_MATCH if
* either or both has no country specified, and the NSNs and extensions are the
* same. Returns SHORT_NSN_MATCH if either or both has no country specified, or
* the country specified is the same, and one NSN could be a shorter version of
* the other number. This includes the case where one has an extension
* specified, and the other does not. Returns NO_MATCH otherwise. For example,
* the numbers +1 345 657 1234 and 657 1234 are a SHORT_NSN_MATCH. The numbers
* +1 345 657 1234 and 345 657 are a NO_MATCH.
*
* @param {i18n.phonenumbers.PhoneNumber|string} firstNumberIn first number to
* compare. If it is a string it can contain formatting, and can have
* country code specified with + at the start.
* @param {i18n.phonenumbers.PhoneNumber|string} secondNumberIn second number to
* compare. If it is a string it can contain formatting, and can have
* country code specified with + at the start.
* @return {i18n.phonenumbers.PhoneNumberUtil.MatchType} NO_MATCH,
* SHORT_NSN_MATCH, NSN_MATCH or EXACT_MATCH depending on the level of
* equality of the two numbers, described in the method definition.
* @throws {i18n.phonenumbers.Error} if either number is not considered to be
* a viable phone number.
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isNumberMatch =
function(firstNumberIn, secondNumberIn) {
/** @type {i18n.phonenumbers.PhoneNumber} */
var firstNumber;
/** @type {i18n.phonenumbers.PhoneNumber} */
var secondNumber;
// If the input arguements are strings parse them to a proto buffer format.
// Else make copies of the phone numbers so that the numbers passed in are not
// edited.
if (typeof firstNumberIn == 'string') {
firstNumber = this.parseHelper_(firstNumberIn, null, false);
} else {
firstNumber = new i18n.phonenumbers.PhoneNumber();
firstNumber.mergeFrom(firstNumberIn);
}
if (typeof secondNumberIn == 'string') {
secondNumber = this.parseHelper_(secondNumberIn, null, false);
} else {
secondNumber = new i18n.phonenumbers.PhoneNumber();
secondNumber.mergeFrom(secondNumberIn);
}
// First clear raw_input and country_code_source field and any empty-string
// extensions so that
// we can use the PhoneNumber.exactlySameAs() method.
firstNumber.clearRawInput();
firstNumber.clearCountryCodeSource();
secondNumber.clearRawInput();
secondNumber.clearCountryCodeSource();
if (firstNumber.hasExtension() && firstNumber.getExtension().length == 0) {
firstNumber.clearExtension();
}
if (secondNumber.hasExtension() && secondNumber.getExtension().length == 0) {
secondNumber.clearExtension();
}
// Early exit if both had extensions and these are different.
if (firstNumber.hasExtension() && secondNumber.hasExtension() &&
firstNumber.getExtension() != secondNumber.getExtension()) {
return i18n.phonenumbers.PhoneNumberUtil.MatchType.NO_MATCH;
}
/** @type {number} */
var firstNumberCountryCode = firstNumber.getCountryCodeOrDefault();
/** @type {number} */
var secondNumberCountryCode = secondNumber.getCountryCodeOrDefault();
// Both had country code specified.
if (firstNumberCountryCode != 0 && secondNumberCountryCode != 0) {
if (firstNumber.exactlySameAs(secondNumber)) {
return i18n.phonenumbers.PhoneNumberUtil.MatchType.EXACT_MATCH;
} else if (firstNumberCountryCode == secondNumberCountryCode &&
this.isNationalNumberSuffixOfTheOther_(firstNumber, secondNumber)) {
// A SHORT_NSN_MATCH occurs if there is a difference because of the
// presence or absence of an 'Italian leading zero', the presence or
// absence of an extension, or one NSN being a shorter variant of the
// other.
return i18n.phonenumbers.PhoneNumberUtil.MatchType.SHORT_NSN_MATCH;
}
// This is not a match.
return i18n.phonenumbers.PhoneNumberUtil.MatchType.NO_MATCH;
}
// Checks cases where one or both country codes were not specified. To make
// equality checks easier, we first set the country codes to be equal.
firstNumber.setCountryCode(0);
secondNumber.setCountryCode(0);
// If all else was the same, then this is an NSN_MATCH.
if (firstNumber.exactlySameAs(secondNumber)) {
return i18n.phonenumbers.PhoneNumberUtil.MatchType.NSN_MATCH;
}
if (this.isNationalNumberSuffixOfTheOther_(firstNumber, secondNumber)) {
return i18n.phonenumbers.PhoneNumberUtil.MatchType.SHORT_NSN_MATCH;
}
return i18n.phonenumbers.PhoneNumberUtil.MatchType.NO_MATCH;
};
/**
* Returns true when one national number is the suffix of the other or both are
* the same.
*
* @param {i18n.phonenumbers.PhoneNumber} firstNumber the first PhoneNumber
* object.
* @param {i18n.phonenumbers.PhoneNumber} secondNumber the second PhoneNumber
* object.
* @return {boolean} true if one PhoneNumber is the suffix of the other one.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.prototype.isNationalNumberSuffixOfTheOther_ =
function(firstNumber, secondNumber) {
/** @type {string} */
var firstNumberNationalNumber = '' + firstNumber.getNationalNumber();
/** @type {string} */
var secondNumberNationalNumber = '' + secondNumber.getNationalNumber();
// Note that endsWith returns true if the numbers are equal.
return goog.string.endsWith(firstNumberNationalNumber,
secondNumberNationalNumber) ||
goog.string.endsWith(secondNumberNationalNumber,
firstNumberNationalNumber);
};
/**
* Check whether the entire input sequence can be matched against the regular
* expression.
*
* @param {RegExp|string} regex the regular expression to match against.
* @param {string} str the string to test.
* @return {boolean} true if str can be matched entirely against regex.
* @private
*/
i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_ = function(regex, str) {
/** @type {Array.<string>} */
var matchedGroups = str.match(regex);
if (matchedGroups && matchedGroups[0].length == str.length) {
return true;
}
return false;
};
/**
* @param {i18n.phonenumbers.PhoneNumber} other
* @return {boolean}
*/
i18n.phonenumbers.PhoneNumber.prototype.exactlySameAs = function(other) {
return other != null &&
this.getCountryCode() == other.getCountryCode() &&
this.getNationalNumber() == other.getNationalNumber() &&
this.getExtension() == other.getExtension() &&
this.getItalianLeadingZero() == other.getItalianLeadingZero() &&
this.getRawInput() == other.getRawInput() &&
this.getCountryCodeSource() == other.getCountryCodeSource();
};
/**
* @param {i18n.phonenumbers.PhoneNumberDesc} other
* @return {boolean}
*/
i18n.phonenumbers.PhoneNumberDesc.prototype.exactlySameAs = function(other) {
return other != null &&
this.getNationalNumberPattern() == other.getNationalNumberPattern() &&
this.getPossibleNumberPattern() == other.getPossibleNumberPattern() &&
this.getExampleNumber() == other.getExampleNumber();
};
/**
* @param {i18n.phonenumbers.PhoneNumber} other
* @return {i18n.phonenumbers.PhoneNumber}
*/
i18n.phonenumbers.PhoneNumber.prototype.mergeFrom = function(other) {
if (other) {
this.values_ = goog.cloneObject(other.values_);
}
return this;
};
/**
* @param {i18n.phonenumbers.NumberFormat} other
* @return {i18n.phonenumbers.NumberFormat}
*/
i18n.phonenumbers.NumberFormat.prototype.mergeFrom = function(other) {
if (other) {
this.values_ = goog.cloneObject(other.values_);
}
return this;
};