// 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.} */ 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.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. 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/; /** * @const * @type {RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.NON_DIGIT_PATTERN_ = /\D/; /** * 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, // A phone number is of type UNKNOWN when it does not fit any of the known // patterns for a specific country. UNKNOWN: 8 }; /** * 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_('' + number.getNationalNumber(), 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.values_ = goog.cloneObject(number.values_); copiedProto.clearExtension(); } else { copiedProto = number; } /** @type {string} */ var nationalSignificantNumber = this.format(copiedProto, i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL); /** @type {!Array.} */ var numberGroups = nationalSignificantNumber.split( i18n.phonenumbers.PhoneNumberUtil.NON_DIGIT_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; } // Note all countries that use leading zero in national number currently don't // use national prefix, so they won't have an area code, which means clients // don't need to worry about appending the leading zero to the geographical // area code they derive from the length we return here. 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 {!goog.string.StringBuffer} */ var formattedNumber = new goog.string.StringBuffer(); /** @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. formattedNumber.append(nationalSignificantNumber); this.formatNumberByFormat_(countryCode, i18n.phonenumbers.PhoneNumberFormat.E164, formattedNumber); return formattedNumber.toString(); } // 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)) { formattedNumber.append(nationalSignificantNumber); return formattedNumber.toString(); } formattedNumber.append(this.formatNationalNumber_(nationalSignificantNumber, regionCode, numberFormat)); this.maybeGetFormattedExtension_(number, regionCode, formattedNumber); this.formatNumberByFormat_(countryCode, numberFormat, formattedNumber); return formattedNumber.toString(); }; /** * 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.} 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 {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) { /** @type {string} */ var nationalPrefix = this.getMetadataForRegion(regionCode).getNationalPrefixOrDefault(); // 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'); numFormat.setNationalPrefixFormattingRule(nationalPrefixFormattingRule); } } /** @type {!goog.string.StringBuffer} */ var formattedNumber = new goog.string.StringBuffer( this.formatAccordingToFormats_(nationalSignificantNumber, userDefinedFormats, numberFormat)); this.maybeGetFormattedExtension_(number, regionCode, formattedNumber); this.formatNumberByFormat_(countryCode, numberFormat, formattedNumber); return formattedNumber.toString(); }; /** * @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 {!goog.string.StringBuffer} */ var formattedNumber = new goog.string.StringBuffer( this.formatNationalNumber_(nationalSignificantNumber, regionCode, i18n.phonenumbers.PhoneNumberFormat.NATIONAL, carrierCode)); this.maybeGetFormattedExtension_(number, regionCode, formattedNumber); this.formatNumberByFormat_(countryCode, i18n.phonenumbers.PhoneNumberFormat.NATIONAL, formattedNumber); return formattedNumber.toString(); } /** * 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(); // 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(); } /** @type {!goog.string.StringBuffer} */ var formattedNumber = new goog.string.StringBuffer(formattedNationalNumber); this.maybeGetFormattedExtension_(number, regionCode, formattedNumber); if (internationalPrefixForFormatting.length > 0) { return internationalPrefixForFormatting + ' ' + countryCode + ' ' + formattedNumber.toString(); } else { this.formatNumberByFormat_(countryCode, i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL, formattedNumber); } return formattedNumber.toString(); }; /** * 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 {!goog.string.StringBuffer} formattedNumber the formatted phone * number. * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.formatNumberByFormat_ = function(countryCode, numberFormat, formattedNumber) { switch (numberFormat) { case i18n.phonenumbers.PhoneNumberFormat.E164: /** @type {string} */ var a = formattedNumber.toString(); formattedNumber.clear(); formattedNumber.append(i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + countryCode + a); return; case i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL: a = formattedNumber.toString(); formattedNumber.clear(); formattedNumber.append(i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN + countryCode + ' ' + a); return; case i18n.phonenumbers.PhoneNumberFormat.NATIONAL: default: return; } }; /** * 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.} */ 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.} */ 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.} 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]; if (!numFormat.hasLeadingDigits() || nationalNumber.search(numFormat.getLeadingDigits()) == 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; }; /** * Appends the formatted extension of a phone number to formattedNumber, if the * phone number had an extension specified. * * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber that might have * an extension. * @param {string} regionCode the ISO 3166-1 two-letter country code. * @param {!goog.string.StringBuffer} formattedNumber the formatted phone * number. * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.maybeGetFormattedExtension_ = function(number, regionCode, formattedNumber) { if (number.hasExtension()) { // 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. /** @type {i18n.phonenumbers.PhoneMetadata} */ var metadata = this.getMetadataForRegion(regionCode); if (metadata.hasPreferredExtnPrefix()) { formattedNumber.append(metadata.getPreferredExtnPrefix()); } else { formattedNumber.append( i18n.phonenumbers.PhoneNumberUtil.DEFAULT_EXTN_PREFIX_); } formattedNumber.append(number.getExtension()); } }; /** * @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(); 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; } /** @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.} */ 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.} 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.} */ 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(); }; /** * 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()); }; /** * 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 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_; }; /** * 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. } */ var a = nationalNumber.match('^' + possibleNumberPattern); /** @type {string} */ var m = a ? a[0] : ''; if (m.length > 0) { return (m.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; } }; /** * 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; for (var i = 1; i <= 3; ++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 {RegExp} */ var validNumberPattern = new RegExp(defaultRegionMetadata.getGeneralDesc() .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(); if (i18n.phonenumbers.PhoneNumberUtil.matchesEntirely_( validNumberPattern, potentialNationalNumberStr)) { 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. } */ var a = numberStr.substring(matchEnd).match( i18n.phonenumbers.PhoneNumberUtil.CAPTURING_DIGIT_PATTERN_); if (a && a[1] != null && a[1].length > 0) { /** @type {string} */ var normalizedGroup = i18n.phonenumbers.PhoneNumberUtil.normalizeHelper_( a[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; } if (numberStr.charAt(0) == i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) { // 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.substring(1))); 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.} */ 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.} */ var a = numberStr.match(i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERN_); /** @type {number} */ var aLength = a.length; for (var i = 1; i < aLength; ++i) { if (a[i] != null && a[i].length > 0) { number.clear(); number.append(numberStr.substring(0, mStart)); return a[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. * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled * with the parsed number. * @throws {i18n.phonenumbers.Error} */ i18n.phonenumbers.PhoneNumberUtil.prototype.parse = function(numberToParse, defaultCountry) { /** @type {i18n.phonenumbers.PhoneNumber} */ var phoneNumber = new i18n.phonenumbers.PhoneNumber(); if (!this.isValidRegionCode_(defaultCountry)) { if (numberToParse.charAt(0) != i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) { throw i18n.phonenumbers.Error.INVALID_COUNTRY_CODE; } } this.parseHelper_(numberToParse, defaultCountry, false, phoneNumber); return phoneNumber; }; /** * 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. * @param {i18n.phonenumbers.PhoneNumber=} opt_phoneNumber mutable PhoneNumber * to decrease object creation when invoked many times. * @return {i18n.phonenumbers.PhoneNumber} a phone number proto buffer filled * with the parsed number. * @throws {i18n.phonenumbers.Error} */ i18n.phonenumbers.PhoneNumberUtil.prototype.parseAndKeepRawInput = function(numberToParse, defaultCountry, opt_phoneNumber) { /** @type {i18n.phonenumbers.PhoneNumber} */ var phoneNumber = opt_phoneNumber != null ? opt_phoneNumber : new i18n.phonenumbers.PhoneNumber(); if (!this.isValidRegionCode_(defaultCountry)) { if (numberToParse.charAt(0) != i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN) { throw i18n.phonenumbers.Error.INVALID_COUNTRY_CODE; } } this.parseHelper_(numberToParse, defaultCountry, true, phoneNumber); return phoneNumber; }; /** * Parses a string and fills up the phoneNumber. 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. * @param {i18n.phonenumbers.PhoneNumber} phoneNumber the PhoneNumber object to * populate. * @throws {i18n.phonenumbers.Error} * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.parseHelper_ = function(numberToParse, defaultCountry, keepRawInput, phoneNumber) { // 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; } 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)); }; /** * 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} */ i18n.phonenumbers.PhoneNumberUtil.prototype.isNumberMatch = function(firstNumberIn, secondNumberIn) { /** @type {i18n.phonenumbers.PhoneNumber} */ var firstNumber = new i18n.phonenumbers.PhoneNumber(); /** @type {i18n.phonenumbers.PhoneNumber} */ var secondNumber = new i18n.phonenumbers.PhoneNumber(); // 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') { this.parseHelper_(firstNumberIn, null, false, firstNumber); } else { firstNumber.values_ = goog.cloneObject(firstNumberIn.values_); } if (typeof secondNumberIn == 'string') { this.parseHelper_(secondNumberIn, null, false, secondNumber); } else { secondNumber.values_ = goog.cloneObject(secondNumberIn.values_); } // 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.} */ var a = str.match(regex); if (a && a[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(); };