UNPKG

google-libphonenumber

Version:

A browserify-compatible wrapper for Google's libphonenumber.

1,542 lines (1,381 loc) 163 kB
/** * @license * Copyright (C) 2010 The Libphonenumber Authors. * * 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). * * NOTE: A lot of methods in this class require Region Code strings. These must * be provided using ISO 3166-1 two-letter country-code format. These should be * in upper-case (but for compatibility lower-case is also allowed). The list of * the codes can be found here: * http://www.iso.org/iso/english_country_names_and_code_elements * * @author Nikolaos Trogkanis */ goog.provide('i18n.phonenumbers.Error'); goog.provide('i18n.phonenumbers.PhoneNumberFormat'); goog.provide('i18n.phonenumbers.PhoneNumberType'); goog.provide('i18n.phonenumbers.PhoneNumberUtil'); goog.provide('i18n.phonenumbers.PhoneNumberUtil.MatchType'); goog.provide('i18n.phonenumbers.PhoneNumberUtil.ValidationResult'); goog.require('goog.array'); goog.require('goog.proto2.PbLiteSerializer'); goog.require('goog.string'); goog.require('goog.string.StringBuffer'); goog.require('i18n.phonenumbers.NumberFormat'); goog.require('i18n.phonenumbers.PhoneMetadata'); goog.require('i18n.phonenumbers.PhoneMetadataCollection'); goog.require('i18n.phonenumbers.PhoneNumber'); goog.require('i18n.phonenumbers.PhoneNumber.CountryCodeSource'); goog.require('i18n.phonenumbers.PhoneNumberDesc'); goog.require('i18n.phonenumbers.metadata'); /** * @constructor * @private */ i18n.phonenumbers.PhoneNumberUtil = function() { /** * A mapping from a region code to the PhoneMetadata for that region. * @type {Object.<string, i18n.phonenumbers.PhoneMetadata>} */ this.regionToMetadataMap = {}; }; goog.addSingletonGetter(i18n.phonenumbers.PhoneNumberUtil); /** * Errors encountered when parsing phone numbers. * * @enum {string} */ i18n.phonenumbers.Error = { INVALID_COUNTRY_CODE: 'Invalid country calling 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 calling code) could have. TOO_SHORT_AFTER_IDD: 'Phone number too short after IDD', // This indicates the string, after any country calling 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_ = 2; /** * The ITU says the maximum length should be 15, but we have found longer * numbers in Germany. * * @const * @type {number} * @private */ i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_FOR_NSN_ = 17; /** * The maximum length of the country calling code. * * @const * @type {number} * @private */ i18n.phonenumbers.PhoneNumberUtil.MAX_LENGTH_COUNTRY_CODE_ = 3; /** * We don't allow input strings for parsing to be longer than 250 chars. This * prevents malicious input from consuming CPU. * * @const * @type {number} * @private */ i18n.phonenumbers.PhoneNumberUtil.MAX_INPUT_STRING_LENGTH_ = 250; /** * Region-code for the unknown region. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.UNKNOWN_REGION_ = 'ZZ'; /** * The prefix that needs to be inserted in front of a Colombian landline number * when dialed from a mobile phone in Colombia. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX_ = '3'; /** * Map of country calling codes that use a mobile token before the area code. * One example of when this is relevant is when determining the length of the * national destination code, which should be the length of the area code plus * the length of the mobile token. * * @const * @type {!Object.<number, string>} * @private */ i18n.phonenumbers.PhoneNumberUtil.MOBILE_TOKEN_MAPPINGS_ = { 52: '1', 54: '9' }; /** * Set of country calling codes that have geographically assigned mobile * numbers. This may not be complete; we add calling codes case by case, as we * find geographical mobile numbers or hear from user reports. * * @const * @type {!Array.<number>} * @private */ i18n.phonenumbers.PhoneNumberUtil.GEO_MOBILE_COUNTRIES_ = [ 52, // Mexico 54, // Argentina 55 // Brazil ]; /** * The PLUS_SIGN signifies the international prefix. * * @const * @type {string} */ i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN = '+'; /** * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.STAR_SIGN_ = '*'; /** * The RFC 3966 format for extensions. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.RFC3966_EXTN_PREFIX_ = ';ext='; /** * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.RFC3966_PREFIX_ = 'tel:'; /** * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.RFC3966_PHONE_CONTEXT_ = ';phone-context='; /** * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.RFC3966_ISDN_SUBADDRESS_ = ';isub='; /** * 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 * @type {!Object.<string, string>} */ 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 '\u06F0': '0', // Eastern-Arabic digit 0 '\u06F1': '1', // Eastern-Arabic digit 1 '\u06F2': '2', // Eastern-Arabic digit 2 '\u06F3': '3', // Eastern-Arabic digit 3 '\u06F4': '4', // Eastern-Arabic digit 4 '\u06F5': '5', // Eastern-Arabic digit 5 '\u06F6': '6', // Eastern-Arabic digit 6 '\u06F7': '7', // Eastern-Arabic digit 7 '\u06F8': '8', // Eastern-Arabic digit 8 '\u06F9': '9' // Eastern-Arabic digit 9 }; /** * A map that contains characters that are essential when dialling. That means * any of the characters in this map must not be removed from a number when * dialling, otherwise the call will not reach the intended destination. * * @const * @type {!Object.<string, string>} * @private */ i18n.phonenumbers.PhoneNumberUtil.DIALLABLE_CHAR_MAPPINGS_ = { '0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '+': i18n.phonenumbers.PhoneNumberUtil.PLUS_SIGN, '*': '*' }; /** * Only upper-case variants of alpha characters are stored. * * @const * @type {!Object.<string, string>} * @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 * @type {!Object.<string, string>} * @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 '\u06F0': '0', // Eastern-Arabic digit 0 '\u06F1': '1', // Eastern-Arabic digit 1 '\u06F2': '2', // Eastern-Arabic digit 2 '\u06F3': '3', // Eastern-Arabic digit 3 '\u06F4': '4', // Eastern-Arabic digit 4 '\u06F5': '5', // Eastern-Arabic digit 5 '\u06F6': '6', // Eastern-Arabic digit 6 '\u06F7': '7', // Eastern-Arabic digit 7 '\u06F8': '8', // Eastern-Arabic digit 8 '\u06F9': '9', // Eastern-Arabic 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' }; /** * Separate map of all symbols that we wish to retain when formatting alpha * numbers. This includes digits, ASCII letters and number grouping symbols such * as '-' and ' '. * * @const * @type {!Object.<string, string>} * @private */ i18n.phonenumbers.PhoneNumberUtil.ALL_PLUS_NUMBER_GROUPING_SYMBOLS_ = { '0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', 'A': 'A', 'B': 'B', 'C': 'C', 'D': 'D', 'E': 'E', 'F': 'F', 'G': 'G', 'H': 'H', 'I': 'I', 'J': 'J', 'K': 'K', 'L': 'L', 'M': 'M', 'N': 'N', 'O': 'O', 'P': 'P', 'Q': 'Q', 'R': 'R', 'S': 'S', 'T': 'T', 'U': 'U', 'V': 'V', 'W': 'W', 'X': 'X', 'Y': 'Y', 'Z': 'Z', 'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z', '-': '-', '\uFF0D': '-', '\u2010': '-', '\u2011': '-', '\u2012': '-', '\u2013': '-', '\u2014': '-', '\u2015': '-', '\u2212': '-', '/': '/', '\uFF0F': '/', ' ': ' ', '\u3000': ' ', '\u2060': ' ', '.': '.', '\uFF0E': '.' }; /** * Pattern that makes it easy to distinguish whether a region has a unique * international dialing prefix or not. If a region 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 region, 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. Full-width * variants are also present. * * @const * @type {string} */ i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION = '-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F \u00A0\u00AD\u200B\u2060\u3000' + '()\uFF08\uFF09\uFF3B\uFF3D.\\[\\]/~\u2053\u223C\uFF5E'; /** * Digits accepted in phone numbers (ascii, fullwidth, arabic-indic, and eastern * arabic digits). * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ = '0-9\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9'; /** * 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} */ i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_PATTERN = new RegExp('[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + ']+'); /** * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.LEADING_PLUS_CHARS_PATTERN_ = new RegExp('^[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + ']+'); /** * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.SEPARATOR_PATTERN_ = '[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + ']+'; /** * @const * @type {!RegExp} */ 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} * @private */ i18n.phonenumbers.PhoneNumberUtil.VALID_START_CHAR_PATTERN_ = new RegExp('[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']'); /** * Regular expression of characters typically used to start a second phone * number for the purposes of parsing. This allows us to strip off parts of the * number that are actually the start of another number, such as for: * (530) 583-6985 x302/x2303 -> the second extension here makes this actually * two phone numbers, (530) 583-6985 x302 and (530) 583-6985 x2303. We remove * the second extension so that the first number is parsed correctly. * * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.SECOND_NUMBER_START_PATTERN_ = /[\\\/] *x/; /** * Regular expression of trailing characters that we want to remove. We remove * all characters that are not alpha or numerical characters. The hash character * is retained here, as it may signify the previous block was an extension. * * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.UNWANTED_END_CHAR_PATTERN_ = new RegExp('[^' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_ + '#]+$'); /** * We use this pattern to check if the phone number has at least three letters * in it - if so, then we treat it as a number where some phone-number digits * are represented by letters. * * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_PHONE_PATTERN_ = /(?:.*?[A-Za-z]){3}.*/; /** * Regular expression of viable phone numbers. This is location independent. * Checks we have at least three leading digits, and only valid punctuation, * alpha characters and digits in the phone number. Does not include extension * data. The symbol 'x' is allowed here as valid punctuation since it is often * used as a placeholder for carrier codes, for example in Brazilian phone * numbers. We also allow multiple '+' characters at the start. * Corresponds to the following: * [digits]{minLengthNsn}| * plus_sign* * (([punctuation]|[star])*[digits]){3,}([punctuation]|[star]|[digits]|[alpha])* * * The first reg-ex is to allow short numbers (two digits long) to be parsed if * they are entered as "15" etc, but only if there is no punctuation in them. * The second expression restricts the number of digits to three or more, but * then allows them to be in international form, and to have alpha-characters * and punctuation. We split up the two reg-exes here and combine them when * creating the reg-ex VALID_PHONE_NUMBER_PATTERN_ itself so we can prefix it * with ^ and append $ to each branch. * * Note VALID_PUNCTUATION starts with a -, so must be the first in the range. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_PHONE_NUMBER_PATTERN_ = '[' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{' + i18n.phonenumbers.PhoneNumberUtil.MIN_LENGTH_FOR_NSN_ + '}'; /** * See MIN_LENGTH_PHONE_NUMBER_PATTERN_ for a full description of this reg-exp. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_ = '[' + i18n.phonenumbers.PhoneNumberUtil.PLUS_CHARS_ + ']*(?:[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + i18n.phonenumbers.PhoneNumberUtil.STAR_SIGN_ + ']*[' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']){3,}[' + i18n.phonenumbers.PhoneNumberUtil.VALID_PUNCTUATION + i18n.phonenumbers.PhoneNumberUtil.STAR_SIGN_ + i18n.phonenumbers.PhoneNumberUtil.VALID_ALPHA_ + 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 region-specific preferences. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.DEFAULT_EXTN_PREFIX_ = ' ext. '; /** * Pattern to capture digits used in an extension. * Places a maximum length of '7' for an extension. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ = '([' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{1,7})'; /** * 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 three regular expressions * here. The first covers RFC 3966 format, where the extension is added using * ';ext='. The second 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! * We allow two options for representing the accented o - the character itself, * and one in the unicode decomposed form with the combining acute accent. * * @const * @type {string} * @private */ i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERNS_FOR_PARSING_ = i18n.phonenumbers.PhoneNumberUtil.RFC3966_EXTN_PREFIX_ + i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ + '|' + '[ \u00A0\\t,]*' + '(?:e?xt(?:ensi(?:o\u0301?|\u00F3))?n?|\uFF45?\uFF58\uFF54\uFF4E?|' + '[,x\uFF58#\uFF03~\uFF5E]|int|anexo|\uFF49\uFF4E\uFF54)' + '[:\\.\uFF0E]?[ \u00A0\\t,-]*' + i18n.phonenumbers.PhoneNumberUtil.CAPTURING_EXTN_DIGITS_ + '#?|' + '[- ]+([' + i18n.phonenumbers.PhoneNumberUtil.VALID_DIGITS_ + ']{1,5})#'; /** * Regexp of all known extension prefixes used by different regions 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.EXTN_PATTERNS_FOR_PARSING_ + ')$', '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.MIN_LENGTH_PHONE_NUMBER_PATTERN_ + '$|' + '^' + i18n.phonenumbers.PhoneNumberUtil.VALID_PHONE_NUMBER_ + '(?:' + i18n.phonenumbers.PhoneNumberUtil.EXTN_PATTERNS_FOR_PARSING_ + ')?' + '$', 'i'); /** * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.NON_DIGITS_PATTERN_ = /\D+/; /** * This was originally set to $1 but there are some countries for which the * first group is not used in the national pattern (e.g. Argentina) so the $1 * group does not match correctly. Therefore, we use \d, so that the first * group actually used in the pattern will be matched. * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_PATTERN_ = /(\$\d)/; /** * @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/; /** * A pattern that is used to determine if the national prefix formatting rule * has the first group only, i.e., does not start with the national prefix. * Note that the pattern explicitly allows for unbalanced parentheses. * @const * @type {!RegExp} * @private */ i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_ONLY_PREFIX_PATTERN_ = /^\(?\$1\)?$/; /** * @const * @type {string} */ i18n.phonenumbers.PhoneNumberUtil.REGION_CODE_FOR_NON_GEO_ENTITY = '001'; /** * INTERNATIONAL and NATIONAL formats are consistent with the definition in * ITU-T Recommendation E123. For example, the number of the Google Switzerland * 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'. RFC3966 is as per * INTERNATIONAL format, but with all spaces and other separating symbols * replaced with a hyphen, and with any phone number extension appended with * ';ext='. It also will have a prefix of 'tel:' added, e.g. * 'tel:+41-44-668-1800'. * * Note: If you are considering storing the number in a neutral format, you are * highly advised to use the PhoneNumber class. * @enum {number} */ i18n.phonenumbers.PhoneNumberFormat = { E164: 0, INTERNATIONAL: 1, NATIONAL: 2, RFC3966: 3 }; /** * Type of phone numbers. * * @enum {number} */ i18n.phonenumbers.PhoneNumberType = { FIXED_LINE: 0, MOBILE: 1, // In some regions (e.g. the USA), it is impossible to distinguish between // fixed-line and mobile numbers by looking at the phone number itself. FIXED_LINE_OR_MOBILE: 2, // Freephone lines TOLL_FREE: 3, PREMIUM_RATE: 4, // The cost of this call is shared between the caller and the recipient, and // is hence typically less than PREMIUM_RATE calls. See // http://en.wikipedia.org/wiki/Shared_Cost_Service for more information. SHARED_COST: 5, // Voice over IP numbers. This includes TSoIP (Telephony Service over IP). VOIP: 6, // A personal number is associated with a particular person, and may be routed // to either a MOBILE or FIXED_LINE number. Some more information can be found // here: http://en.wikipedia.org/wiki/Personal_Numbers PERSONAL_NUMBER: 7, PAGER: 8, // Used for 'Universal Access Numbers' or 'Company Numbers'. They may be // further routed to specific offices, but allow one number to be used for a // company. UAN: 9, // Used for 'Voice Mail Access Numbers'. VOICEMAIL: 10, // A phone number is of type UNKNOWN when it does not fit any of the known // patterns for a specific region. UNKNOWN: -1 }; /** * Types of phone number matches. See detailed description beside the * isNumberMatch() method. * * @enum {number} */ i18n.phonenumbers.PhoneNumberUtil.MatchType = { NOT_A_NUMBER: 0, NO_MATCH: 1, SHORT_NSN_MATCH: 2, NSN_MATCH: 3, EXACT_MATCH: 4 }; /** * 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 cannot 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 2 * 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: * Punctuation is stripped. * For ALPHA/VANITY numbers: * 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. * For other numbers: * Wide-ascii digits are converted to normal ASCII (European) digits. * Arabic-Indic numerals are converted to European numerals. * Spurious alpha characters are stripped. * * @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.normalizeDigitsOnly(number); } }; /** * 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 * {@code 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: * * <pre> * var phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance(); * var number = phoneUtil.parse('16502530000', 'US'); * var nationalSignificantNumber = * phoneUtil.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; * } * </pre> * * 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 {@code national_number} instead. Read the following carefully before * deciding to use this method: * <ul> * <li> geographical area codes change over time, and this method honors those * changes; therefore, it doesn't guarantee the stability of the result it * produces. * <li> subscriber numbers may not be diallable from all devices (notably * mobile devices, which typically requires the full national_number to be * dialled in most regions). * <li> most non-geographical numbers have no area codes, including numbers * from non-geographical entities. * <li> some geographical numbers have no area codes. * </ul> * * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for * which clients want to know the length of the area code. * @return {number} the length of area code of the PhoneNumber object passed in. */ i18n.phonenumbers.PhoneNumberUtil.prototype.getLengthOfGeographicalAreaCode = function(number) { /** @type {i18n.phonenumbers.PhoneMetadata} */ var metadata = this.getMetadataForRegion(this.getRegionCodeForNumber(number)); if (metadata == null) { return 0; } // If a country doesn't use a national prefix, and this number doesn't have // an Italian leading zero, we assume it is a closed dialling plan with no // area codes. if (!metadata.hasNationalPrefix() && !number.hasItalianLeadingZero()) { return 0; } if (!this.isNumberGeographical(number)) { return 0; } return this.getLengthOfNationalDestinationCode(number); }; /** * Gets the length of the national destination code (NDC) from the PhoneNumber * object passed in, so that clients could use it to split a national * significant number into NDC and subscriber number. The NDC of a phone number * is normally the first group of digit(s) right after the country calling code * when the number is formatted in the international format, if there is a * subscriber number part that follows. An example of how this could be used: * * <pre> * var phoneUtil = i18n.phonenumbers.PhoneNumberUtil.getInstance(); * var number = phoneUtil.parse('18002530000', 'US'); * var nationalSignificantNumber = * phoneUtil.getNationalSignificantNumber(number); * var nationalDestinationCode; * var subscriberNumber; * * var nationalDestinationCodeLength = * phoneUtil.getLengthOfNationalDestinationCode(number); * if (nationalDestinationCodeLength > 0) { * nationalDestinationCode = * nationalSignificantNumber.substring(0, nationalDestinationCodeLength); * subscriberNumber = * nationalSignificantNumber.substring(nationalDestinationCodeLength); * } else { * nationalDestinationCode = ''; * subscriberNumber = nationalSignificantNumber; * } * </pre> * * Refer to the unittests to see the difference between this function and * {@link #getLengthOfGeographicalAreaCode}. * * @param {i18n.phonenumbers.PhoneNumber} number the PhoneNumber object for * which clients want to know the length of the NDC. * @return {number} the length of NDC of the PhoneNumber object passed in. */ i18n.phonenumbers.PhoneNumberUtil.prototype.getLengthOfNationalDestinationCode = function(number) { /** @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 = number.clone(); copiedProto.clearExtension(); } else { copiedProto = number; } /** @type {string} */ var nationalSignificantNumber = this.format(copiedProto, i18n.phonenumbers.PhoneNumberFormat.INTERNATIONAL); /** @type {!Array.<string>} */ var numberGroups = nationalSignificantNumber.split( i18n.phonenumbers.PhoneNumberUtil.NON_DIGITS_PATTERN_); // The pattern will start with '+COUNTRY_CODE ' so the first group will always // be the empty string (before the + symbol) and the second group will be the // country calling code. The third group will be area code if it is 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; } if (this.getNumberType(number) == i18n.phonenumbers.PhoneNumberType.MOBILE) { // For example Argentinian mobile numbers, when formatted in the // international format, are in the form of +54 9 NDC XXXX.... As a result, // we take the length of the third group (NDC) and add the length of the // mobile token, which also forms part of the national significant number. // This assumes that the mobile token is always formatted separately from // the rest of the phone number. /** @type {string} */ var mobileToken = i18n.phonenumbers.PhoneNumberUtil.getCountryMobileToken( number.getCountryCodeOrDefault()); if (mobileToken != '') { return numberGroups[2].length + mobileToken.length; } } return numberGroups[1].length; }; /** * Returns the mobile token for the provided country calling code if it has * one, otherwise returns an empty string. A mobile token is a number inserted * before the area code when dialing a mobile number from that country from * abroad. * * @param {number} countryCallingCode the country calling code for which we * want the mobile token. * @return {string} the mobile token for the given country calling code. */ i18n.phonenumbers.PhoneNumberUtil.getCountryMobileToken = function(countryCallingCode) { return i18n.phonenumbers.PhoneNumberUtil.MOBILE_TOKEN_MAPPINGS_[ countryCallingCode] || ''; }; /** * Convenience method to get a list of what regions the library has metadata * for. * @return {!Array.<string>} region codes supported by the library. */ i18n.phonenumbers.PhoneNumberUtil.prototype.getSupportedRegions = function() { return goog.array.filter( Object.keys(i18n.phonenumbers.metadata.countryToMetadata), function(regionCode) { return isNaN(regionCode); }); }; /** * Convenience method to get a list of what global network calling codes the * library has metadata for. * @return {!Array.<number>} global network calling codes supported by the * library. */ i18n.phonenumbers.PhoneNumberUtil.prototype. getSupportedGlobalNetworkCallingCodes = function() { var callingCodesAsStrings = goog.array.filter( Object.keys(i18n.phonenumbers.metadata.countryToMetadata), function(regionCode) { return !isNaN(regionCode); }); return goog.array.map(callingCodesAsStrings, function(callingCode) { return parseInt(callingCode, 10); }); }; /** * 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.<string, string>} 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 if the national prefix formatting rule has the first * group only, i.e., does not start with the national prefix. * * @param {string} nationalPrefixFormattingRule The formatting rule for the * national prefix. * @return {boolean} true if the national prefix formatting rule has the first * group only. */ i18n.phonenumbers.PhoneNumberUtil.prototype.formattingRuleHasFirstGroupOnly = function(nationalPrefixFormattingRule) { return nationalPrefixFormattingRule.length == 0 || i18n.phonenumbers.PhoneNumberUtil.FIRST_GROUP_ONLY_PREFIX_PATTERN_. test(nationalPrefixFormattingRule); }; /** * Tests whether a phone number has a geographical association. It checks if * the number is associated to a certain region in the country where it belongs * to. Note that this doesn't verify if the number is actually in use. * * @param {i18n.phonenumbers.PhoneNumber} phoneNumber The phone number to test. * @return {boolean} true if the phone number has a geographical association. */ i18n.phonenumbers.PhoneNumberUtil.prototype.isNumberGeographical = function(phoneNumber) { /** @type {i18n.phonenumbers.PhoneNumberType} */ var numberType = this.getNumberType(phoneNumber); return numberType == i18n.phonenumbers.PhoneNumberType.FIXED_LINE || numberType == i18n.phonenumbers.PhoneNumberType.FIXED_LINE_OR_MOBILE || (goog.array.contains( i18n.phonenumbers.PhoneNumberUtil.GEO_MOBILE_COUNTRIES_, phoneNumber.getCountryCodeOrDefault()) && numberType == i18n.phonenumbers.PhoneNumberType.MOBILE); }; /** * Helper function to check region code is not unknown or null. * * @param {?string} regionCode the ISO 3166-1 two-letter region code. * @return {boolean} true if region code is valid. * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.isValidRegionCode_ = function(regionCode) { // In Java we check whether the regionCode is contained in supportedRegions // that is built out of all the values of countryCallingCodeToRegionCodeMap // (countryCodeToRegionCodeMap in JS) minus REGION_CODE_FOR_NON_GEO_ENTITY. // In JS we check whether the regionCode is contained in the keys of // countryToMetadata but since for non-geographical country calling codes // (e.g. +800) we use the country calling codes instead of the region code as // key in the map we have to make sure regionCode is not a number to prevent // returning true for non-geographical country calling codes. return regionCode != null && isNaN(regionCode) && regionCode.toUpperCase() in i18n.phonenumbers.metadata.countryToMetadata; }; /** * Helper function to check the country calling code is valid. * * @param {number} countryCallingCode the country calling code. * @return {boolean} true if country calling code code is valid. * @private */ i18n.phonenumbers.PhoneNumberUtil.prototype.hasValidCountryCallingCode_ = function(countryCallingCode) { return countryCallingCode in i18n.phonenumbers.metadata.countryCodeToRegionCodeMap; }; /** * 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 calling code of 0 or an otherwise invalid * country calling 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) { if (number.getNationalNumber() == 0 && number.hasRawInput()) { // Unparseable numbers that kept their raw input just use that. // This is the only case where a number can be formatted as E164 without a // leading '+' symbol (but the original number wasn't parseable anyway). // TODO: Consider removing the 'if' above so that unparseable strings // without raw input format to the empty string instead of "+00" /** @type {string} */ var rawInput = number.getRawInputOrDefault(); if (rawInput.length > 0) { return rawInput; } } /** @type {number} */ var countryCallingCode = number.getCountryCodeOrDefault(); /** @type {string} */ var nationalSignificantNumber = this.getNationalSignificantNumber(number); if (numberFormat == i18n.phonenumbers.PhoneNumberFormat.E164) { // Early exit for E164 case (even if the country calling code is invalid) // since no formatting of the national number needs to be applied. // Extensions are not formatted. return this.prefixNumberWithCountryCallingCode_( countryCallingCode, i18n.phonenumbers.PhoneNumberFormat.E164, nationalSignificantNumber, ''); } if (!this.hasValidCountryCallingCode_(countryCallingCode)) { return nationalSignificantNumber; } // Note getRegionCodeForCountryCode() is used because formatting information // for regions which share a country calling code is contained by only one // region for performance reasons. For example, for NANPA regions it will be // contained in the metadata for US. /** @type {string} */ var regionCode = this.getRegionCodeForCountryCode(countryCallingCode); // Metadata cannot be null because the country calling code is valid (which // means that the region code cannot be ZZ and must be one of our supported // region codes). /** @type {i18n.phonenumbers.PhoneMetadata} */ var metadata = this.getMetadataForRegionOrCallingCode_(countryCallingCode, regionCode); /** @type {string} */ var formattedExtension = this.maybeGetFormattedExtension_(number, metadata, numberFormat); /** @type {string} */ var formattedNationalNumber = this.formatNsn_(nationalSignificantNumber, metadata, numberFormat); return this.prefixNumberWithCountryCallingCode_(countryCallingCode, numberFormat, formattedNationalNumber, formattedExtension); }; /** * Formats a phone number in the specified format using client-defined * formatting rules. Note that if the phone number has a country calling code of * zero or an otherwise invalid country calling code, we cannot work out things * like whether there should be a national prefix applied, or how to format * extensions, so we return the national significant number with no formatting * applied. * * @param {i18n.phonenumbers.PhoneNumber} number the phone number to be * formatted. * @param {i18n.phonenumbers.PhoneNumberFormat} numberFormat the format the * phone number should be formatted into. * @param {Array.<i18n.phonenumbers.NumberFormat>} userDefinedFormats formatting * rules specified by clients. * @return {string} the formatted phone number. */ i18n.phonenumbers.PhoneNumberUtil.prototype.formatByPattern = function(number, numberFormat, userDefinedFormats) { /** @type {number} */ var countryCallingCode = number.getCountryCodeOrDefault(); /** @type {string} */ var nationalSignificantNumber = this.getNationalSignificantNumber(number); if (!this.hasValidCountryCallingCode_(countryCallingCode)) { return nationalSignificantNumber; } // Note getRegionCodeForCountryCode() is used because formatting information // for regions which share a country calling code is contained by only one // region for performance reasons. For example, for NANPA regions it will be // contained in the metadata for US. /** @typ