phonenumbers.go (2,193 lines of code) (raw):

package phonenumbers import ( "errors" "fmt" "reflect" "regexp" "strconv" "strings" "sync" "unicode" "github.com/nyaruka/phonenumbers/gen" "golang.org/x/text/language" "golang.org/x/text/language/display" "google.golang.org/protobuf/proto" ) const ( // MIN_LENGTH_FOR_NSN is the minimum and maximum length of the national significant number. MIN_LENGTH_FOR_NSN = 2 // MAX_LENGTH_FOR_NSN: The ITU says the maximum length should be 15, but we have // found longer numbers in Germany. MAX_LENGTH_FOR_NSN = 17 // MAX_LENGTH_COUNTRY_CODE is the maximum length of the country calling code. MAX_LENGTH_COUNTRY_CODE = 3 // MAX_INPUT_STRING_LENGTH caps input strings for parsing at 250 chars. // This prevents malicious input from overflowing the regular-expression // engine. MAX_INPUT_STRING_LENGTH = 250 // UNKNOWN_REGION is the region-code for the unknown region. UNKNOWN_REGION = "ZZ" NANPA_COUNTRY_CODE = 1 // The prefix that needs to be inserted in front of a Colombian // landline number when dialed from a mobile phone in Colombia. COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX = "3" // The PLUS_SIGN signifies the international prefix. PLUS_SIGN = '+' STAR_SIGN = '*' RFC3966_EXTN_PREFIX = ";ext=" RFC3966_PREFIX = "tel:" RFC3966_PHONE_CONTEXT = ";phone-context=" RFC3966_ISDN_SUBADDRESS = ";isub=" // 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. VALID_PUNCTUATION = "-x\u2010-\u2015\u2212\u30FC\uFF0D-\uFF0F " + "\u00A0\u00AD\u200B\u2060\u3000()\uFF08\uFF09\uFF3B\uFF3D." + "\\[\\]/~\u2053\u223C\uFF5E" DIGITS = "\\p{Nd}" // We accept alpha characters in phone numbers, ASCII only, upper // and lower case. VALID_ALPHA = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" PLUS_CHARS = "+\uFF0B" // This is defined by ICU as the unknown time zone. UNKNOWN_TIMEZONE = "Etc/Unknown" ) var ( // 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. MOBILE_TOKEN_MAPPINGS = map[int]string{ 52: "1", 54: "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. DIALLABLE_CHAR_MAPPINGS = map[rune]rune{ '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '0': '0', PLUS_SIGN: PLUS_SIGN, '*': '*', } // Only upper-case variants of alpha characters are stored. ALPHA_MAPPINGS = map[rune]rune{ '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. ALPHA_PHONE_MAPPINGS = map[rune]rune{ '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '0': '0', PLUS_SIGN: PLUS_SIGN, '*': '*', '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 " ". ALL_PLUS_NUMBER_GROUPING_SYMBOLS = map[rune]rune{ '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '0': '0', PLUS_SIGN: PLUS_SIGN, '*': '*', '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. UNIQUE_INTERNATIONAL_PREFIX = regexp.MustCompile("[\\d]+(?:[~\u2053\u223C\uFF5E][\\d]+)?") PLUS_CHARS_PATTERN = regexp.MustCompile("[" + PLUS_CHARS + "]+") SEPARATOR_PATTERN = regexp.MustCompile("[" + VALID_PUNCTUATION + "]+") NOT_SEPARATOR_PATTERN = regexp.MustCompile("[^" + VALID_PUNCTUATION + "]+") CAPTURING_DIGIT_PATTERN = regexp.MustCompile("(" + 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. VALID_START_CHAR = "[" + PLUS_CHARS + DIGITS + "]" VALID_START_CHAR_PATTERN = regexp.MustCompile(VALID_START_CHAR) // 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. SECOND_NUMBER_START = "[\\\\/] *x" SECOND_NUMBER_START_PATTERN = regexp.MustCompile(SECOND_NUMBER_START) // 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. UNWANTED_END_CHARS = "[[\\P{N}&&\\P{L}]&&[^#]]+$" UNWANTED_END_CHAR_PATTERN = regexp.MustCompile(UNWANTED_END_CHARS) // 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. VALID_ALPHA_PHONE_PATTERN = regexp.MustCompile("^(?:.*?[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. // // Note VALID_PUNCTUATION starts with a -, so must be the first in the range. VALID_PHONE_NUMBER = DIGITS + "{" + strconv.Itoa(MIN_LENGTH_FOR_NSN) + "}" + "|" + "[" + PLUS_CHARS + "]*(?:[" + VALID_PUNCTUATION + string(STAR_SIGN) + "]*" + DIGITS + "){3,}[" + VALID_PUNCTUATION + string(STAR_SIGN) + VALID_ALPHA + 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. DEFAULT_EXTN_PREFIX = " ext. " // Pattern to capture digits used in an extension. Places a maximum // length of "7" for an extension. CAPTURING_EXTN_DIGITS = "(" + 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! Canonical-equivalence doesn't // seem to be an option with Android java, so we allow two options // for representing the accented o - the character itself, and one in // the unicode decomposed form with the combining acute accent. EXTN_PATTERNS_FOR_PARSING = RFC3966_EXTN_PREFIX + 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,-]*" + CAPTURING_EXTN_DIGITS + "#?|" + "[- ]+(" + DIGITS + "{1,5})#" EXTN_PATTERNS_FOR_MATCHING = RFC3966_EXTN_PREFIX + 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,-]*" + CAPTURING_EXTN_DIGITS + "#?|" + "[- ]+(" + DIGITS + "{1,5})#" // Regexp of all known extension prefixes used by different regions // followed by 1 or more valid digits, for use when parsing. EXTN_PATTERN = regexp.MustCompile("(?:" + EXTN_PATTERNS_FOR_PARSING + ")$") // 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. VALID_PHONE_NUMBER_PATTERN = regexp.MustCompile( "^(" + VALID_PHONE_NUMBER + "(?:" + EXTN_PATTERNS_FOR_PARSING + ")?)$") NON_DIGITS_PATTERN = regexp.MustCompile(`(\D+)`) DIGITS_PATTERN = regexp.MustCompile(`(\d+)`) // The FIRST_GROUP_PATTERN 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. FIRST_GROUP_PATTERN = regexp.MustCompile(`(\$\d)`) NP_PATTERN = regexp.MustCompile(`\$NP`) FG_PATTERN = regexp.MustCompile(`\$FG`) CC_PATTERN = regexp.MustCompile(`\$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. FIRST_GROUP_ONLY_PREFIX_PATTERN = regexp.MustCompile(`\(?\$1\)?`) REGION_CODE_FOR_NON_GEO_ENTITY = "001" // Regular expression of valid global-number-digits for the phone-context parameter, following the // syntax defined in RFC3966. RFC3966_VISUAL_SEPARATOR = "[\\-\\.\\(\\)]?" RFC3966_PHONE_DIGIT = "(" + DIGITS + "|" + RFC3966_VISUAL_SEPARATOR + ")" RFC3966_GLOBAL_NUMBER_DIGITS = "^\\" + string(PLUS_SIGN) + RFC3966_PHONE_DIGIT + "*" + DIGITS + RFC3966_PHONE_DIGIT + "*$" RFC3966_GLOBAL_NUMBER_DIGITS_PATTERN = regexp.MustCompile(RFC3966_GLOBAL_NUMBER_DIGITS) // Regular expression of valid domainname for the phone-context parameter, following the syntax // defined in RFC3966. ALPHANUM = VALID_ALPHA + DIGITS RFC3966_DOMAINLABEL = "[" + ALPHANUM + "]+((\\-)*[" + ALPHANUM + "])*" RFC3966_TOPLABEL = "[" + VALID_ALPHA + "]+((\\-)*[" + ALPHANUM + "])*" RFC3966_DOMAINNAME = "^(" + RFC3966_DOMAINLABEL + "\\.)*" + RFC3966_TOPLABEL + "\\.?$" RFC3966_DOMAINNAME_PATTERN = regexp.MustCompile(RFC3966_DOMAINNAME) // Set of country codes that have geographically assigned mobile numbers (see GEO_MOBILE_COUNTRIES // below) which are not based on *area codes*. For example, in China mobile numbers start with a // carrier indicator, and beyond that are geographically assigned: this carrier indicator is not // considered to be an area code. GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES = map[int32]bool{ 86: true, // China } // Set of country codes that doesn't have national prefix, but it has area codes. COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES = map[int32]bool{ 52: true, // Mexico } ) // 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. type PhoneNumberFormat int const ( E164 PhoneNumberFormat = iota INTERNATIONAL NATIONAL RFC3966 ) type PhoneNumberType int const ( // NOTES: // // FIXED_LINE_OR_MOBILE: // 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. // SHARED_COST: // 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. // VOIP: // Voice over IP numbers. This includes TSoIP (Telephony Service over IP). // PERSONAL_NUMBER: // 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 // UAN: // 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. // VOICEMAIL: // Used for "Voice Mail Access Numbers". // UNKNOWN: // A phone number is of type UNKNOWN when it does not fit any of // the known patterns for a specific region. FIXED_LINE PhoneNumberType = iota MOBILE FIXED_LINE_OR_MOBILE TOLL_FREE PREMIUM_RATE SHARED_COST VOIP PERSONAL_NUMBER PAGER UAN VOICEMAIL UNKNOWN ) type MatchType int const ( NOT_A_NUMBER MatchType = iota NO_MATCH SHORT_NSN_MATCH NSN_MATCH EXACT_MATCH ) type ValidationResult int const ( IS_POSSIBLE ValidationResult = iota INVALID_COUNTRY_CODE TOO_SHORT TOO_LONG IS_POSSIBLE_LOCAL_ONLY INVALID_LENGTH ) // TODO(ttacon): leniency comments? type Leniency int const ( POSSIBLE Leniency = iota VALID STRICT_GROUPING EXACT_GROUPING ) func (l Leniency) Verify(number *PhoneNumber, candidate string) bool { switch l { case POSSIBLE: return IsPossibleNumber(number) case VALID: if !IsValidNumber(number) || !ContainsOnlyValidXChars(number, candidate) { return false } return IsNationalPrefixPresentIfRequired(number) case STRICT_GROUPING: if !IsValidNumber(number) || !ContainsOnlyValidXChars(number, candidate) || ContainsMoreThanOneSlashInNationalNumber(number, candidate) || !IsNationalPrefixPresentIfRequired(number) { return false } return CheckNumberGroupingIsValid(number, candidate, func(number *PhoneNumber, normalizedCandidate string, expectedNumberGroups []string) bool { return AllNumberGroupsRemainGrouped( number, normalizedCandidate, expectedNumberGroups) }) case EXACT_GROUPING: if !IsValidNumber(number) || !ContainsOnlyValidXChars(number, candidate) || ContainsMoreThanOneSlashInNationalNumber(number, candidate) || !IsNationalPrefixPresentIfRequired(number) { return false } return CheckNumberGroupingIsValid(number, candidate, func(number *PhoneNumber, normalizedCandidate string, expectedNumberGroups []string) bool { return AllNumberGroupsAreExactlyPresent( number, normalizedCandidate, expectedNumberGroups) }) } return false } var ( // golang map is not go routine safe. Sometimes process exiting // because of panic. So adding mutex to synchronize the operation. // The set of regions that share country calling code 1. // There are roughly 26 regions. nanpaRegions = make(map[string]struct{}) // A mapping from a region code to the PhoneMetadata for that region. // Note: Synchronization, though only needed for the Android version // of the library, is used in all versions for consistency. regionToMetadataMap = make(map[string]*PhoneMetadata) // A mapping from a country calling code for a non-geographical // entity to the PhoneMetadata for that country calling code. // Examples of the country calling codes include 800 (International // Toll Free Service) and 808 (International Shared Cost Service). // Note: Synchronization, though only needed for the Android version // of the library, is used in all versions for consistency. countryCodeToNonGeographicalMetadataMap = make(map[int]*PhoneMetadata) // A cache for frequently used region-specific regular expressions. // The initial capacity is set to 100 as this seems to be an optimal // value for Android, based on performance measurements. regexCache = make(map[string]*regexp.Regexp) regCacheMutex sync.RWMutex // The set of regions the library supports. // There are roughly 240 of them and we set the initial capacity of // the HashSet to 320 to offer a load factor of roughly 0.75. supportedRegions = make(map[string]bool, 320) // The set of calling codes that map to the non-geo entity // region ("001"). This set currently contains < 12 elements so the // default capacity of 16 (load factor=0.75) is fine. countryCodesForNonGeographicalRegion = make(map[int]bool, 16) // These are our onces and maps for our prefix to carrier maps carrierOnces = make(map[string]*sync.Once) carrierPrefixMap = make(map[string]*intStringMap) // These are our onces and maps for our prefix to geocoding maps geocodingOnces = make(map[string]*sync.Once) geocodingPrefixMap = make(map[string]*intStringMap) // All the calling codes we support supportedCallingCodes = make(map[int]bool, 320) // Our once and map for prefix to timezone lookups timezoneOnce sync.Once timezoneMap *intStringArrayMap // Our map from country code (as integer) to two letter region codes countryCodeToRegion map[int][]string ) var ErrEmptyMetadata = errors.New("empty metadata") func readFromRegexCache(key string) (*regexp.Regexp, bool) { regCacheMutex.RLock() v, ok := regexCache[key] regCacheMutex.RUnlock() return v, ok } func writeToRegexCache(key string, value *regexp.Regexp) { regCacheMutex.Lock() regexCache[key] = value regCacheMutex.Unlock() } func regexFor(pattern string) *regexp.Regexp { regex, found := readFromRegexCache(pattern) if !found { regex = regexp.MustCompile(pattern) writeToRegexCache(pattern, regex) } return regex } func readFromNanpaRegions(key string) (struct{}, bool) { v, ok := nanpaRegions[key] return v, ok } func writeToNanpaRegions(key string, val struct{}) { nanpaRegions[key] = val } func readFromRegionToMetadataMap(key string) (*PhoneMetadata, bool) { v, ok := regionToMetadataMap[key] return v, ok } func writeToRegionToMetadataMap(key string, val *PhoneMetadata) { regionToMetadataMap[key] = val } func readFromCountryCodeToNonGeographicalMetadataMap(key int) (*PhoneMetadata, bool) { v, ok := countryCodeToNonGeographicalMetadataMap[key] return v, ok } func writeToCountryCodeToNonGeographicalMetadataMap(key int, v *PhoneMetadata) { countryCodeToNonGeographicalMetadataMap[key] = v } func loadMetadataFromFile( regionCode string, countryCallingCode int) error { metadataCollection, err := MetadataCollection() if err != nil { return err } else if currMetadataColl == nil { currMetadataColl = metadataCollection } metadataList := metadataCollection.GetMetadata() if len(metadataList) == 0 { return ErrEmptyMetadata } for _, meta := range metadataList { region := meta.GetId() if region == "001" { // it's a non geographical entity writeToCountryCodeToNonGeographicalMetadataMap(int(meta.GetCountryCode()), meta) } else { writeToRegionToMetadataMap(region, meta) } } return nil } var ( currMetadataColl *PhoneMetadataCollection reloadMetadata = true ) func MetadataCollection() (*PhoneMetadataCollection, error) { if !reloadMetadata { return currMetadataColl, nil } rawBytes, err := decodeUnzipString(gen.NumberData) if err != nil { return nil, err } var metadataCollection = &PhoneMetadataCollection{} err = proto.Unmarshal(rawBytes, metadataCollection) reloadMetadata = false return metadataCollection, err } // 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. func extractPossibleNumber(number string) string { if VALID_START_CHAR_PATTERN.MatchString(number) { start := VALID_START_CHAR_PATTERN.FindIndex([]byte(number))[0] number = number[start:] // Remove trailing non-alpha non-numerical characters. indices := UNWANTED_END_CHAR_PATTERN.FindIndex([]byte(number)) if len(indices) > 0 { number = number[0:indices[0]] } // Check for extra numbers at the end. indices = SECOND_NUMBER_START_PATTERN.FindIndex([]byte(number)) if len(indices) > 0 { number = number[0:indices[0]] } return number } return "" } // 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. // @VisibleForTesting func isViablePhoneNumber(number string) bool { if len(number) < MIN_LENGTH_FOR_NSN { return false } return VALID_PHONE_NUMBER_PATTERN.MatchString(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. func normalize(number string) string { if VALID_ALPHA_PHONE_PATTERN.MatchString(number) { return normalizeHelper(number, ALPHA_PHONE_MAPPINGS, true) } return NormalizeDigitsOnly(number) } // 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. func NormalizeDigitsOnly(number string) string { return normalizeDigits(number, false /* strip non-digits */) } // ugly hack still, but fills out the functionality (sort of) // TODO(ttacon): more completely/elegantly solve this var arabicIndicNumberals = map[rune]rune{ '٠': '0', '۰': '0', '١': '1', '۱': '1', '٢': '2', '۲': '2', '٣': '3', '۳': '3', '٤': '4', '۴': '4', '٥': '5', '۵': '5', '٦': '6', '۶': '6', '٧': '7', '۷': '7', '٨': '8', '۸': '8', '٩': '9', '۹': '9', '\uFF10': '0', '\uFF11': '1', '\uFF12': '2', '\uFF13': '3', '\uFF14': '4', '\uFF15': '5', '\uFF16': '6', '\uFF17': '7', '\uFF18': '8', '\uFF19': '9', } func normalizeDigits(number string, keepNonDigits bool) string { buf := number var normalizedDigits = NewBuilder(nil) for _, c := range buf { if unicode.IsDigit(c) { if v, ok := arabicIndicNumberals[c]; ok { normalizedDigits.WriteRune(v) } else { normalizedDigits.WriteRune(c) } } else if keepNonDigits { normalizedDigits.WriteRune(c) } } return normalizedDigits.String() } // Normalizes a string of characters representing a phone number. This // strips all characters which are not diallable on a mobile phone // keypad (including all non-ASCII digits). func normalizeDiallableCharsOnly(number string) string { return normalizeHelper( number, DIALLABLE_CHAR_MAPPINGS, true /* remove non matches */) } // Converts all alpha characters in a number to their respective digits // on a keypad, but retains existing formatting. func ConvertAlphaCharactersInNumber(number string) string { return normalizeHelper(number, ALPHA_PHONE_MAPPINGS, false) } // Gets the length of the geographical area code from 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: // // number, err := Parse("16502530000", "US"); // // ... deal with err appropriately ... // nationalSignificantNumber := GetNationalSignificantNumber(number); // var areaCode, subscriberNumber; // // int areaCodeLength = GetLengthOfGeographicalAreaCode(number); // if (areaCodeLength > 0) { // areaCode = nationalSignificantNumber[0:areaCodeLength]; // subscriberNumber = nationalSignificantNumber[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 regions). // - most non-geographical numbers have no area codes, including numbers from // non-geographical entities // - some geographical numbers have no area codes. func GetLengthOfGeographicalAreaCode(number *PhoneNumber) int { metadata := getMetadataForRegion(GetRegionCodeForNumber(number)) if metadata == nil { return 0 } numType := GetNumberType(number) countryCallingCode := number.GetCountryCode() // 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. // Note:this is our general assumption, but there are exceptions which are tracked in // COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES. if len(metadata.GetNationalPrefix()) == 0 && !number.GetItalianLeadingZero() && !COUNTRIES_WITHOUT_NATIONAL_PREFIX_WITH_AREA_CODES[countryCallingCode] { return 0 } // Note this is a rough heuristic; it doesn't cover Indonesia well, for example, where area // codes are present for some mobile phones but not for others. We have no better way of // representing this in the metadata at this point. if numType == MOBILE && GEO_MOBILE_COUNTRIES_WITHOUT_MOBILE_AREA_CODES[countryCallingCode] { return 0 } if !isNumberGeographical(number) { return 0 } return 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: // // PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance(); // PhoneNumber number = phoneUtil.parse("18002530000", "US"); // String nationalSignificantNumber = phoneUtil.GetNationalSignificantNumber(number); // String nationalDestinationCode; // String subscriberNumber; // // int nationalDestinationCodeLength = // phoneUtil.GetLengthOfNationalDestinationCode(number); // if nationalDestinationCodeLength > 0 { // nationalDestinationCode = nationalSignificantNumber.substring(0, // nationalDestinationCodeLength); // subscriberNumber = nationalSignificantNumber.substring( // nationalDestinationCodeLength); // } else { // nationalDestinationCode = ""; // subscriberNumber = nationalSignificantNumber; // } // // Refer to the unittests to see the difference between this function and // GetLengthOfGeographicalAreaCode(). func GetLengthOfNationalDestinationCode(number *PhoneNumber) int { var copiedProto *PhoneNumber if len(number.GetExtension()) > 0 { // 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 = &PhoneNumber{} proto.Merge(copiedProto, number) copiedProto.Extension = nil } else { copiedProto = number } nationalSignificantNumber := Format(copiedProto, INTERNATIONAL) numberGroups := NON_DIGITS_PATTERN.Split(nationalSignificantNumber, -1) // 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. if len(numberGroups) <= 3 { return 0 } if GetNumberType(number) == 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 second group (which is 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. mobileToken := GetCountryMobileToken(int(number.GetCountryCode())) if mobileToken != "" { return len(numberGroups[1]) + len(numberGroups[2]) } } return len(numberGroups[2]) } // 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. func GetCountryMobileToken(countryCallingCode int) string { if val, ok := MOBILE_TOKEN_MAPPINGS[countryCallingCode]; ok { return val } return "" } // 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. func normalizeHelper(number string, normalizationReplacements map[rune]rune, removeNonMatches bool) string { var normalizedNumber = NewBuilder(nil) for _, character := range number { newDigit, ok := normalizationReplacements[unicode.ToUpper(character)] if ok { normalizedNumber.WriteRune(newDigit) } else if !removeNonMatches { normalizedNumber.WriteRune(character) } // If neither of the above are true, we remove this character. } return normalizedNumber.String() } // GetSupportedRegions returns all regions the library has metadata for. func GetSupportedRegions() map[string]bool { return supportedRegions } // GetSupportedCallingCodes returns all country calling codes the library has metadata for, covering both non-geographical // entities (global network calling codes) and those used for geographical entities. This could be // used to populate a drop-down box of country calling codes for a phone-number widget, for // instance. func GetSupportedCallingCodes() map[int]bool { return supportedCallingCodes } // GetSupportedGlobalNetworkCallingCodes returns all global network calling codes the library has metadata for. func GetSupportedGlobalNetworkCallingCodes() map[int]bool { return countryCodesForNonGeographicalRegion } // Helper function to check if the national prefix formatting rule has the // first group only, i.e., does not start with the national prefix. func formattingRuleHasFirstGroupOnly(nationalPrefixFormattingRule string) bool { return len(nationalPrefixFormattingRule) == 0 || FIRST_GROUP_ONLY_PREFIX_PATTERN.MatchString(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. // // A similar method is implemented as PhoneNumberOfflineGeocoder.canBeGeocoded, // which performs a looser check, since it only prevents cases where prefixes // overlap for geocodable and non-geocodable numbers. Also, if new phone // number types were added, we should check if this other method should be // updated too. func isNumberGeographical(phoneNumber *PhoneNumber) bool { numberType := GetNumberType(phoneNumber) // TODO: Include mobile phone numbers from countries like Indonesia, // which has some mobile numbers that are geographical. return numberType == FIXED_LINE || numberType == FIXED_LINE_OR_MOBILE } // Helper function to check region code is not unknown or null. func isValidRegionCode(regionCode string) bool { valid := supportedRegions[regionCode] return len(regionCode) != 0 && valid } // Helper function to check the country calling code is valid. func hasValidCountryCallingCode(countryCallingCode int) bool { _, containsKey := countryCodeToRegion[countryCallingCode] return containsKey } // 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. func Format(number *PhoneNumber, numberFormat PhoneNumberFormat) string { if number.GetNationalNumber() == 0 && len(number.GetRawInput()) > 0 { // 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" rawInput := number.GetRawInput() if len(rawInput) > 0 { return rawInput } } var formattedNumber = NewBuilder(nil) FormatWithBuf(number, numberFormat, formattedNumber) return formattedNumber.String() } // Same as Format(PhoneNumber, PhoneNumberFormat), but accepts a mutable // StringBuilder as a parameter to decrease object creation when invoked // many times. func FormatWithBuf(number *PhoneNumber, numberFormat PhoneNumberFormat, formattedNumber *Builder) { // Clear the StringBuilder first. formattedNumber.Reset() countryCallingCode := int(number.GetCountryCode()) nationalSignificantNumber := GetNationalSignificantNumber(number) if numberFormat == 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. formattedNumber.WriteString(nationalSignificantNumber) prefixNumberWithCountryCallingCode(countryCallingCode, E164, formattedNumber) return } else if !hasValidCountryCallingCode(countryCallingCode) { formattedNumber.WriteString(nationalSignificantNumber) return } // 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. regionCode := 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). metadata := getMetadataForRegionOrCallingCode(countryCallingCode, regionCode) formattedNumber.WriteString(formatNsn(nationalSignificantNumber, metadata, numberFormat)) maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber) prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber) } // FormatByPattern 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. func FormatByPattern(number *PhoneNumber, numberFormat PhoneNumberFormat, userDefinedFormats []*NumberFormat) string { countryCallingCode := int(number.GetCountryCode()) nationalSignificantNumber := GetNationalSignificantNumber(number) if !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. regionCode := GetRegionCodeForCountryCode(countryCallingCode) // Metadata cannot be null because the country calling code is valid metadata := getMetadataForRegionOrCallingCode(countryCallingCode, regionCode) formattedNumber := NewBuilder(nil) formattingPattern := chooseFormattingPatternForNumber( userDefinedFormats, nationalSignificantNumber) if formattingPattern == nil { // If no pattern above is matched, we format the number as a whole. formattedNumber.WriteString(nationalSignificantNumber) } else { numFormatCopy := &NumberFormat{} // Before we do a replacement of the national prefix pattern // $NP with the national prefix, we need to copy the rule so // that subsequent replacements for different numbers have the // appropriate national prefix. proto.Merge(numFormatCopy, formattingPattern) nationalPrefixFormattingRule := formattingPattern.GetNationalPrefixFormattingRule() if len(nationalPrefixFormattingRule) > 0 { nationalPrefix := metadata.GetNationalPrefix() if len(nationalPrefix) > 0 { // Replace $NP with national prefix and $FG with the // first group ($1). nationalPrefixFormattingRule = NP_PATTERN.ReplaceAllString( nationalPrefixFormattingRule, nationalPrefix) nationalPrefixFormattingRule = FG_PATTERN.ReplaceAllString( nationalPrefixFormattingRule, "\\$1") numFormatCopy.NationalPrefixFormattingRule = &nationalPrefixFormattingRule } else { // We don't want to have a rule for how to format the // national prefix if there isn't one. numFormatCopy.NationalPrefixFormattingRule = nil } } formattedNumber.WriteString( formatNsnUsingPattern( nationalSignificantNumber, numFormatCopy, numberFormat)) } maybeAppendFormattedExtension(number, metadata, numberFormat, formattedNumber) prefixNumberWithCountryCallingCode(countryCallingCode, numberFormat, formattedNumber) return formattedNumber.String() } // Formats a phone number in national format for dialing using the carrier // as specified in the carrierCode. The carrierCode will always be used // regardless of whether the phone number already has a preferred domestic // carrier code stored. If carrierCode contains an empty string, returns // the number in national format without any carrier code. func FormatNationalNumberWithCarrierCode(number *PhoneNumber, carrierCode string) string { countryCallingCode := int(number.GetCountryCode()) nationalSignificantNumber := GetNationalSignificantNumber(number) if !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. regionCode := GetRegionCodeForCountryCode(countryCallingCode) // Metadata cannot be null because the country calling code is valid. metadata := getMetadataForRegionOrCallingCode(countryCallingCode, regionCode) formattedNumber := NewBuilder(nil) formattedNumber.WriteString( formatNsnWithCarrier( nationalSignificantNumber, metadata, NATIONAL, carrierCode)) maybeAppendFormattedExtension(number, metadata, NATIONAL, formattedNumber) prefixNumberWithCountryCallingCode( countryCallingCode, NATIONAL, formattedNumber) return formattedNumber.String() } func getMetadataForRegionOrCallingCode(countryCallingCode int, regionCode string) *PhoneMetadata { if REGION_CODE_FOR_NON_GEO_ENTITY == regionCode { return getMetadataForNonGeographicalRegion(countryCallingCode) } return getMetadataForRegion(regionCode) } // Formats a phone number in national format for dialing using the carrier // as specified in the preferredDomesticCarrierCode field of the PhoneNumber // object passed in. If that is missing, use the fallbackCarrierCode passed // in instead. If there is no preferredDomesticCarrierCode, and the // fallbackCarrierCode contains an empty string, return the number in // national format without any carrier code. // // Use formatNationalNumberWithCarrierCode instead if the carrier code // passed in should take precedence over the number's // preferredDomesticCarrierCode when formatting. func FormatNationalNumberWithPreferredCarrierCode( number *PhoneNumber, fallbackCarrierCode string) string { pref := number.GetPreferredDomesticCarrierCode() if number.GetPreferredDomesticCarrierCode() == "" { pref = fallbackCarrierCode } return FormatNationalNumberWithCarrierCode(number, pref) } // Returns a number formatted in such a way that it can be dialed from a // mobile phone in a specific region. If the number cannot be reached from // the region (e.g. some countries block toll-free numbers from being // called outside of the country), the method returns an empty string. func FormatNumberForMobileDialing( number *PhoneNumber, regionCallingFrom string, withFormatting bool) string { countryCallingCode := int(number.GetCountryCode()) if !hasValidCountryCallingCode(countryCallingCode) { return number.GetRawInput() // go impl defaults to "" } formattedNumber := "" // Clear the extension, as that part cannot normally be dialed // together with the main number. var numberNoExt = &PhoneNumber{} proto.Merge(numberNoExt, number) numberNoExt.Extension = nil // can we assume this is safe? (no nil-pointer?) regionCode := GetRegionCodeForCountryCode(countryCallingCode) numberType := GetNumberType(numberNoExt) isValidNumber := numberType != UNKNOWN if regionCallingFrom == regionCode { isFixedLineOrMobile := numberType == FIXED_LINE || numberType == MOBILE || numberType == FIXED_LINE_OR_MOBILE // Carrier codes may be needed in some countries. We handle this here. if regionCode == "CO" && numberType == FIXED_LINE { formattedNumber = FormatNationalNumberWithCarrierCode( numberNoExt, COLOMBIA_MOBILE_TO_FIXED_LINE_PREFIX) } else if regionCode == "BR" && isFixedLineOrMobile { if numberNoExt.GetPreferredDomesticCarrierCode() != "" { formattedNumber = FormatNationalNumberWithPreferredCarrierCode(numberNoExt, "") } else { // Brazilian fixed line and mobile numbers need to be dialed // with a carrier code when called within Brazil. Without // that, most of the carriers won't connect the call. // Because of that, we return an empty string here. formattedNumber = "" } } else if isValidNumber && regionCode == "HU" { // The national format for HU numbers doesn't contain the // national prefix, because that is how numbers are normally // written down. However, the national prefix is obligatory when // dialing from a mobile phone, except for short numbers. As a // result, we add it back here // if it is a valid regular length phone number. formattedNumber = GetNddPrefixForRegion(regionCode, true /* strip non-digits */) + " " + Format(numberNoExt, NATIONAL) } else if countryCallingCode == NANPA_COUNTRY_CODE { // For NANPA countries, we output international format for // numbers that can be dialed internationally, since that // always works, except for numbers which might potentially be // short numbers, which are always dialled in national format. regionMetadata := getMetadataForRegion(regionCallingFrom) if canBeInternationallyDialled(numberNoExt) && testNumberLength(GetNationalSignificantNumber(numberNoExt), regionMetadata, UNKNOWN) != TOO_SHORT { formattedNumber = Format(numberNoExt, INTERNATIONAL) } else { formattedNumber = Format(numberNoExt, NATIONAL) } } else { // For non-geographical countries, and Mexican and Chilean fixed // line and mobile numbers, we output international format for // numbers that can be dialed internationally as that always // works. // MX fixed line and mobile numbers should always be formatted // in international format, even when dialed within MX. For // national format to work, a carrier code needs to be used, // and the correct carrier code depends on if the caller and // callee are from the same local area. It is trickier to get // that to work correctly than using international format, which // is tested to work fine on all carriers. CL fixed line // numbers need the national prefix when dialing in the national // format, but don't have it when used for display. The reverse // is true for mobile numbers. As a result, we output them in // the international format to make it work. if regionCode == REGION_CODE_FOR_NON_GEO_ENTITY || ((regionCode == "MX" || regionCode == "CL" || regionCode == "UZ") && isFixedLineOrMobile) && canBeInternationallyDialled(numberNoExt) { formattedNumber = Format(numberNoExt, INTERNATIONAL) } else { formattedNumber = Format(numberNoExt, NATIONAL) } } } else if isValidNumber && canBeInternationallyDialled(numberNoExt) { // We assume that short numbers are not diallable from outside // their region, so if a number is not a valid regular length // phone number, we treat it as if it cannot be internationally // dialled. if withFormatting { return Format(numberNoExt, INTERNATIONAL) } return Format(numberNoExt, E164) } if withFormatting { return formattedNumber } return normalizeDiallableCharsOnly(formattedNumber) } // Formats a phone number for out-of-country dialing purposes. If no // regionCallingFrom is supplied, we format the number in its // INTERNATIONAL format. If the country calling code is the same as that // of the region where the number is from, then NATIONAL formatting will // be applied. // // If the number itself has a country calling code of zero or an otherwise // invalid country calling 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 calling code). // In those cases, no international prefix is used. For regions which have // multiple international prefixes, the number in its INTERNATIONAL format // will be returned instead. func FormatOutOfCountryCallingNumber( number *PhoneNumber, regionCallingFrom string) string { if !isValidRegionCode(regionCallingFrom) { return Format(number, INTERNATIONAL) } countryCallingCode := int(number.GetCountryCode()) nationalSignificantNumber := GetNationalSignificantNumber(number) if !hasValidCountryCallingCode(countryCallingCode) { return nationalSignificantNumber } if countryCallingCode == NANPA_COUNTRY_CODE { if IsNANPACountry(regionCallingFrom) { // For NANPA regions, return the national format for these // regions but prefix it with the country calling code. return strconv.Itoa(countryCallingCode) + " " + Format(number, NATIONAL) } } else if countryCallingCode == getCountryCodeForValidRegion(regionCallingFrom) { // If regions share a country calling code, the country calling // code need not be dialled. This also applies when dialling // within a region, 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 calling code. // Details here: http://www.petitfute.com/voyage/225-info-pratiques-reunion return Format(number, NATIONAL) } // Metadata cannot be null because we checked 'isValidRegionCode()' above. metadataForRegionCallingFrom := getMetadataForRegion(regionCallingFrom) internationalPrefix := metadataForRegionCallingFrom.GetInternationalPrefix() // For regions that have multiple international prefixes, the // international format of the number is returned, unless there is // a preferred international prefix. internationalPrefixForFormatting := "" metPref := metadataForRegionCallingFrom.GetPreferredInternationalPrefix() if UNIQUE_INTERNATIONAL_PREFIX.MatchString(internationalPrefix) { internationalPrefixForFormatting = internationalPrefix } else if metPref != "" { internationalPrefixForFormatting = metPref } regionCode := GetRegionCodeForCountryCode(countryCallingCode) // Metadata cannot be null because the country calling code is valid. metadataForRegion := getMetadataForRegionOrCallingCode(countryCallingCode, regionCode) formattedNationalNumber := formatNsn( nationalSignificantNumber, metadataForRegion, INTERNATIONAL) formattedNumber := NewBuilder([]byte(formattedNationalNumber)) maybeAppendFormattedExtension(number, metadataForRegion, INTERNATIONAL, formattedNumber) if len(internationalPrefixForFormatting) > 0 { formattedNumber.InsertString(0, internationalPrefixForFormatting+" "+ strconv.Itoa(countryCallingCode)+" ") } else { prefixNumberWithCountryCallingCode( countryCallingCode, INTERNATIONAL, formattedNumber) } return formattedNumber.String() } // 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. When the number contains a leading zero and this is // unexpected for this country, or we don't have a formatting pattern for // the number, the method returns the raw input when it is available. // // Note this method guarantees no digit will be inserted, removed or // modified as a result of formatting. func FormatInOriginalFormat(number *PhoneNumber, regionCallingFrom string) string { rawInput := number.GetRawInput() if len(rawInput) == 0 && !hasFormattingPatternForNumber(number) { // We check if we have the formatting pattern because without that, we might format the number // as a group without national prefix. return rawInput } if number.GetCountryCodeSource() == 0 { return Format(number, NATIONAL) } var formattedNumber string switch number.GetCountryCodeSource() { case PhoneNumber_FROM_NUMBER_WITH_PLUS_SIGN: formattedNumber = Format(number, INTERNATIONAL) case PhoneNumber_FROM_NUMBER_WITH_IDD: formattedNumber = FormatOutOfCountryCallingNumber(number, regionCallingFrom) case PhoneNumber_FROM_NUMBER_WITHOUT_PLUS_SIGN: formattedNumber = Format(number, INTERNATIONAL)[1:] case PhoneNumber_FROM_DEFAULT_COUNTRY: // Fall-through to default case. fallthrough default: regionCode := GetRegionCodeForCountryCode(int(number.GetCountryCode())) // We strip non-digits from the NDD here, and from the raw // input later, so that we can compare them easily. nationalPrefix := GetNddPrefixForRegion( regionCode, true /* strip non-digits */) nationalFormat := Format(number, NATIONAL) if len(nationalPrefix) == 0 { // If the region doesn't have a national prefix at all, // we can safely return the national format without worrying // about a national prefix being added. formattedNumber = nationalFormat break } // Otherwise, we check if the original number was entered with // a national prefix. if rawInputContainsNationalPrefix(rawInput, nationalPrefix, regionCode) { // If so, we can safely return the national format. formattedNumber = nationalFormat break } // Metadata cannot be null here because GetNddPrefixForRegion() // (above) returns null if there is no metadata for the region. metadata := getMetadataForRegion(regionCode) nationalNumber := GetNationalSignificantNumber(number) formatRule := chooseFormattingPatternForNumber(metadata.GetNumberFormat(), nationalNumber) // The format rule could still be null here if the national // number was 0 and there was no raw input (this should not // be possible for numbers generated by the phonenumber library // as they would also not have a country calling code and we // would have exited earlier). if formatRule == nil { formattedNumber = nationalFormat break } // When the format we apply to this number doesn't contain // national prefix, we can just return the national format. // TODO: Refactor the code below with the code in // isNationalPrefixPresentIfRequired. candidateNationalPrefixRule := formatRule.GetNationalPrefixFormattingRule() // We assume that the first-group symbol will never be _before_ // the national prefix. indexOfFirstGroup := strings.Index(candidateNationalPrefixRule, "$1") if indexOfFirstGroup <= 0 { formattedNumber = nationalFormat break } candidateNationalPrefixRule = candidateNationalPrefixRule[0:indexOfFirstGroup] candidateNationalPrefixRule = NormalizeDigitsOnly(candidateNationalPrefixRule) if len(candidateNationalPrefixRule) == 0 { // National prefix not used when formatting this number. formattedNumber = nationalFormat break } // Otherwise, we need to remove the national prefix from our output. numFormatCopy := &NumberFormat{} proto.Merge(numFormatCopy, formatRule) numFormatCopy.NationalPrefixFormattingRule = nil var numberFormats = []*NumberFormat{numFormatCopy} formattedNumber = FormatByPattern(number, NATIONAL, numberFormats) } rawInput = number.GetRawInput() // If no digit is inserted/removed/modified as a result of our // formatting, we return the formatted phone number; otherwise we // return the raw input the user entered. if len(formattedNumber) != 0 && len(rawInput) > 0 { normalizedFormattedNumber := normalizeDiallableCharsOnly(formattedNumber) normalizedRawInput := normalizeDiallableCharsOnly(rawInput) if normalizedFormattedNumber != normalizedRawInput { formattedNumber = rawInput } } return formattedNumber } // Check if rawInput, which is assumed to be in the national format, has // a national prefix. The national prefix is assumed to be in digits-only // form. func rawInputContainsNationalPrefix(rawInput, nationalPrefix, regionCode string) bool { normalizedNationalNumber := NormalizeDigitsOnly(rawInput) if strings.HasPrefix(normalizedNationalNumber, nationalPrefix) { // Some Japanese numbers (e.g. 00777123) might be mistaken to // contain the national prefix when written without it // (e.g. 0777123) if we just do prefix matching. To tackle that, // we check the validity of the number if the assumed national // prefix is removed (777123 won't be valid in Japan). num, err := Parse(normalizedNationalNumber[len(nationalPrefix):], regionCode) if err != nil { return false } return IsValidNumber(num) } return false } func hasFormattingPatternForNumber(number *PhoneNumber) bool { countryCallingCode := int(number.GetCountryCode()) phoneNumberRegion := GetRegionCodeForCountryCode(countryCallingCode) metadata := getMetadataForRegionOrCallingCode( countryCallingCode, phoneNumberRegion) if metadata == nil { return false } nationalNumber := GetNationalSignificantNumber(number) formatRule := chooseFormattingPatternForNumber( metadata.GetNumberFormat(), nationalNumber) return formatRule != nil } // Formats a phone number for out-of-country dialing purposes. // // Note that in this version, if the number was entered originally using // alpha characters and this version of the number is stored in raw_input, // this representation of the number will be used rather than the digit // representation. Grouping information, as specified by characters // such as "-" and " ", will be retained. // // Caveats: // // - This will not produce good results if the country calling code is // both present in the raw input _and_ is the start of the national // number. This is not a problem in the regions which typically use // alpha numbers. // - This will also not produce good results if the raw input has any // grouping information within the first three digits of the national // number, and if the function needs to strip preceding digits/words // in the raw input before these digits. Normally people group the // first three digits together so this is not a huge problem - and will // be fixed if it proves to be so. func FormatOutOfCountryKeepingAlphaChars( number *PhoneNumber, regionCallingFrom string) string { rawInput := number.GetRawInput() // If there is no raw input, then we can't keep alpha characters // because there aren't any. In this case, we return // formatOutOfCountryCallingNumber. if len(rawInput) == 0 { return FormatOutOfCountryCallingNumber(number, regionCallingFrom) } countryCode := int(number.GetCountryCode()) if !hasValidCountryCallingCode(countryCode) { return rawInput } // Strip any prefix such as country calling code, IDD, that was // present. We do this by comparing the number in raw_input with // the parsed number. To do this, first we normalize punctuation. // We retain number grouping symbols such as " " only. rawInput = normalizeHelper(rawInput, ALL_PLUS_NUMBER_GROUPING_SYMBOLS, true) // Now we trim everything before the first three digits in the // parsed number. We choose three because all valid alpha numbers // have 3 digits at the start - if it does not, then we don't trim // anything at all. Similarly, if the national number was less than // three digits, we don't trim anything at all. nationalNumber := GetNationalSignificantNumber(number) if len(nationalNumber) > 3 { firstNationalNumberDigit := strings.Index(rawInput, nationalNumber[0:3]) if firstNationalNumberDigit > -1 { rawInput = rawInput[firstNationalNumberDigit:] } } metadataForRegionCallingFrom := getMetadataForRegion(regionCallingFrom) if countryCode == NANPA_COUNTRY_CODE { if IsNANPACountry(regionCallingFrom) { return strconv.Itoa(countryCode) + " " + rawInput } } else if metadataForRegionCallingFrom != nil && countryCode == getCountryCodeForValidRegion(regionCallingFrom) { formattingPattern := chooseFormattingPatternForNumber( metadataForRegionCallingFrom.GetNumberFormat(), nationalNumber) if formattingPattern == nil { // If no pattern above is matched, we format the original input. return rawInput } newFormat := &NumberFormat{} proto.Merge(newFormat, formattingPattern) // The first group is the first group of digits that the user // wrote together. newFormat.Pattern = proto.String("(\\d+)(.*)") // Here we just concatenate them back together after the national // prefix has been fixed. newFormat.Format = proto.String("$1$2") // Now we format using this pattern instead of the default pattern, // but with the national prefix prefixed if necessary. This will not // work in the cases where the pattern (and not the leading digits) // decide whether a national prefix needs to be used, since we // have overridden the pattern to match anything, but that is not // the case in the metadata to date. return formatNsnUsingPattern(rawInput, newFormat, NATIONAL) } var internationalPrefixForFormatting = "" // If an unsupported region-calling-from is entered, or a country // with multiple international prefixes, the international format // of the number is returned, unless there is a preferred international // prefix. if metadataForRegionCallingFrom != nil { internationalPrefix := metadataForRegionCallingFrom.GetInternationalPrefix() internationalPrefixForFormatting = internationalPrefix if !UNIQUE_INTERNATIONAL_PREFIX.MatchString(internationalPrefix) { internationalPrefixForFormatting = metadataForRegionCallingFrom.GetPreferredInternationalPrefix() } } var formattedNumber = NewBuilder([]byte(rawInput)) regionCode := GetRegionCodeForCountryCode(countryCode) // Metadata cannot be null because the country calling code is valid. var metadataForRegion *PhoneMetadata = getMetadataForRegionOrCallingCode(countryCode, regionCode) maybeAppendFormattedExtension(number, metadataForRegion, INTERNATIONAL, formattedNumber) if len(internationalPrefixForFormatting) > 0 { formattedNumber.InsertString(0, internationalPrefixForFormatting+" "+ strconv.Itoa(countryCode)+" ") } else { // Invalid region entered as country-calling-from (so no metadata // was found for it) or the region chosen has multiple international // dialling prefixes. prefixNumberWithCountryCallingCode(countryCode, INTERNATIONAL, formattedNumber) } return formattedNumber.String() } // Gets the national significant number of the a phone number. Note a // national significant number doesn't contain a national prefix or // any formatting. func GetNationalSignificantNumber(number *PhoneNumber) string { // If leading zero(s) have been set, we prefix this now. Note this // is not a national prefix. nationalNumber := NewBuilder(nil) if number.GetItalianLeadingZero() { zeros := make([]byte, number.GetNumberOfLeadingZeros()) for i := range zeros { zeros[i] = '0' } nationalNumber.Write(zeros) } asStr := strconv.FormatUint(number.GetNationalNumber(), 10) nationalNumber.WriteString(asStr) return nationalNumber.String() } // A helper function that is used by format and formatByPattern. func prefixNumberWithCountryCallingCode( countryCallingCode int, numberFormat PhoneNumberFormat, formattedNumber *Builder) { // TODO(ttacon): add some sort of BulkWrite builder to Builder // also that name isn't too awesome...:) newBuf := NewBuilder(nil) switch numberFormat { case E164: newBuf.WriteString(string(PLUS_SIGN)) newBuf.Write(strconv.AppendInt([]byte{}, int64(countryCallingCode), 10)) newBuf.Write(formattedNumber.Bytes()) case INTERNATIONAL: newBuf.WriteString(string(PLUS_SIGN)) newBuf.Write(strconv.AppendInt([]byte{}, int64(countryCallingCode), 10)) newBuf.WriteString(" ") newBuf.Write(formattedNumber.Bytes()) case RFC3966: newBuf.WriteString(RFC3966_PREFIX) newBuf.WriteString(string(PLUS_SIGN)) newBuf.Write(strconv.AppendInt([]byte{}, int64(countryCallingCode), 10)) newBuf.WriteString("-") newBuf.Write(formattedNumber.Bytes()) case NATIONAL: fallthrough default: newBuf.Write(formattedNumber.Bytes()) } formattedNumber.ResetWith(newBuf.Bytes()) } // Simple wrapper of formatNsn for the common case of no carrier code. func formatNsn( number string, metadata *PhoneMetadata, numberFormat PhoneNumberFormat) string { return formatNsnWithCarrier(number, metadata, numberFormat, "") } // Note in some regions, 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. func formatNsnWithCarrier(number string, metadata *PhoneMetadata, numberFormat PhoneNumberFormat, carrierCode string) string { var intlNumberFormats []*NumberFormat = metadata.GetIntlNumberFormat() // When the intlNumberFormats exists, we use that to format national // number for the INTERNATIONAL format instead of using the // numberDesc.numberFormats. var availableFormats []*NumberFormat = metadata.GetIntlNumberFormat() if len(intlNumberFormats) == 0 || numberFormat == NATIONAL { availableFormats = metadata.GetNumberFormat() } var formattingPattern *NumberFormat = chooseFormattingPatternForNumber(availableFormats, number) if formattingPattern == nil { return number } return formatNsnUsingPatternWithCarrier( number, formattingPattern, numberFormat, carrierCode) } func chooseFormattingPatternForNumber( availableFormats []*NumberFormat, nationalNumber string) *NumberFormat { for _, numFormat := range availableFormats { leadingDigitsPattern := numFormat.GetLeadingDigitsPattern() size := len(leadingDigitsPattern) patP := `^(?:` + numFormat.GetPattern() + `)$` // Strictly match m := regexFor(patP) if size == 0 { mat := m.FindString(nationalNumber) if m.MatchString(nationalNumber) && len(mat) == len(nationalNumber) { return numFormat } else { continue } } // We always use the last leading_digits_pattern, as it is the // most detailed. reg := regexFor(leadingDigitsPattern[size-1]) inds := reg.FindStringIndex(nationalNumber) if len(inds) > 0 && inds[0] == 0 && m.MatchString(nationalNumber) { // inds[0] == 0 ensures strict match of leading digits return numFormat } } return nil } // Simple wrapper of formatNsnUsingPattern for the common case of no carrier code. func formatNsnUsingPattern( nationalNumber string, formattingPattern *NumberFormat, numberFormat PhoneNumberFormat) string { return formatNsnUsingPatternWithCarrier( nationalNumber, formattingPattern, numberFormat, "") } // Note that carrierCode is optional - if null or an empty string, no // carrier code replacement will take place. func formatNsnUsingPatternWithCarrier( nationalNumber string, formattingPattern *NumberFormat, numberFormat PhoneNumberFormat, carrierCode string) string { numberFormatRule := formattingPattern.GetFormat() m := regexFor(formattingPattern.GetPattern()) formattedNationalNumber := "" if numberFormat == NATIONAL && len(carrierCode) > 0 && len(formattingPattern.GetDomesticCarrierCodeFormattingRule()) > 0 { // Replace the $CC in the formatting rule with the desired carrier code. carrierCodeFormattingRule := formattingPattern.GetDomesticCarrierCodeFormattingRule() i := 1 carrierCodeFormattingRule = CC_PATTERN.ReplaceAllStringFunc(carrierCodeFormattingRule, func(s string) string { if i > 0 { i -= 1 return carrierCode } return s }) // Now replace the $FG in the formatting rule with the first group // and the carrier code combined in the appropriate way. i = 1 numberFormatRule = FIRST_GROUP_PATTERN.ReplaceAllStringFunc( numberFormatRule, func(s string) string { if i > 0 { i -= 1 return carrierCodeFormattingRule } return s }) formattedNationalNumber = m.ReplaceAllString(numberFormatRule, nationalNumber) } else { // Use the national prefix formatting rule instead. nationalPrefixFormattingRule := formattingPattern.GetNationalPrefixFormattingRule() if numberFormat == NATIONAL && len(nationalPrefixFormattingRule) > 0 { i := 1 fgp := FIRST_GROUP_PATTERN.ReplaceAllStringFunc(numberFormatRule, func(s string) string { if i > 0 { i -= 1 return nationalPrefixFormattingRule } return s }) formattedNationalNumber = m.ReplaceAllString(nationalNumber, fgp) } else { formattedNationalNumber = m.ReplaceAllString( nationalNumber, numberFormatRule, ) } } if numberFormat == RFC3966 { // Strip any leading punctuation. inds := SEPARATOR_PATTERN.FindStringIndex(formattedNationalNumber) if len(inds) > 0 && inds[0] == 0 { formattedNationalNumber = formattedNationalNumber[inds[1]:] } allStr := NOT_SEPARATOR_PATTERN.FindAllString(formattedNationalNumber, -1) formattedNationalNumber = strings.Join(allStr, "-") } return formattedNationalNumber } // Gets a valid number for the specified region. func GetExampleNumber(regionCode string) *PhoneNumber { return GetExampleNumberForType(regionCode, FIXED_LINE) } // Gets a valid number for the specified region and number type. func GetExampleNumberForType(regionCode string, typ PhoneNumberType) *PhoneNumber { // Check the region code is valid. if !isValidRegionCode(regionCode) { return nil } // PhoneNumberDesc (pointer?) var desc = getNumberDescByType(getMetadataForRegion(regionCode), typ) exNum := desc.GetExampleNumber() if len(exNum) > 0 { num, err := Parse(exNum, regionCode) if err != nil { return nil } return num } return nil } // Gets a valid number for the specified country calling code for a non-geographical entity. func GetExampleNumberForNonGeoEntity(countryCallingCode int) *PhoneNumber { var metadata *PhoneMetadata = getMetadataForNonGeographicalRegion(countryCallingCode) if metadata == nil { return nil } // For geographical entities, fixed-line data is always present. However, for non-geographical // entities, this is not the case, so we have to go through different types to find the // example number. descPriority := []*PhoneNumberDesc{metadata.GetMobile(), metadata.GetTollFree(), metadata.GetSharedCost(), metadata.GetVoip(), metadata.GetVoicemail(), metadata.GetUan(), metadata.GetPremiumRate()} for _, desc := range descPriority { if desc != nil && desc.GetExampleNumber() != "" { num, err := Parse("+"+strconv.Itoa(countryCallingCode)+desc.GetExampleNumber(), "ZZ") if err != nil { return nil } return num } } return nil } // Appends the formatted extension of a phone number to formattedNumber, // if the phone number had an extension specified. func maybeAppendFormattedExtension( number *PhoneNumber, metadata *PhoneMetadata, numberFormat PhoneNumberFormat, formattedNumber *Builder) { extension := number.GetExtension() if len(extension) == 0 { return } prefExtn := metadata.GetPreferredExtnPrefix() if numberFormat == RFC3966 { formattedNumber.WriteString(RFC3966_EXTN_PREFIX) } else if len(prefExtn) > 0 { formattedNumber.WriteString(prefExtn) } else { formattedNumber.WriteString(DEFAULT_EXTN_PREFIX) } formattedNumber.WriteString(extension) } func getNumberDescByType( metadata *PhoneMetadata, typ PhoneNumberType) *PhoneNumberDesc { switch typ { case PREMIUM_RATE: return metadata.GetPremiumRate() case TOLL_FREE: return metadata.GetTollFree() case MOBILE: return metadata.GetMobile() case FIXED_LINE: fallthrough case FIXED_LINE_OR_MOBILE: return metadata.GetFixedLine() case SHARED_COST: return metadata.GetSharedCost() case VOIP: return metadata.GetVoip() case PERSONAL_NUMBER: return metadata.GetPersonalNumber() case PAGER: return metadata.GetPager() case UAN: return metadata.GetUan() case VOICEMAIL: return metadata.GetVoicemail() default: return metadata.GetGeneralDesc() } } // Gets the type of a phone number. func GetNumberType(number *PhoneNumber) PhoneNumberType { var regionCode string = GetRegionCodeForNumber(number) var metadata *PhoneMetadata = getMetadataForRegionOrCallingCode( int(number.GetCountryCode()), regionCode) if metadata == nil { return UNKNOWN } var nationalSignificantNumber = GetNationalSignificantNumber(number) return getNumberTypeHelper(nationalSignificantNumber, metadata) } func getNumberTypeHelper(nationalNumber string, metadata *PhoneMetadata) PhoneNumberType { if !isNumberMatchingDesc(nationalNumber, metadata.GetGeneralDesc()) { return UNKNOWN } if isNumberMatchingDesc(nationalNumber, metadata.GetPremiumRate()) { return PREMIUM_RATE } if isNumberMatchingDesc(nationalNumber, metadata.GetTollFree()) { return TOLL_FREE } if isNumberMatchingDesc(nationalNumber, metadata.GetSharedCost()) { return SHARED_COST } if isNumberMatchingDesc(nationalNumber, metadata.GetVoip()) { return VOIP } if isNumberMatchingDesc(nationalNumber, metadata.GetPersonalNumber()) { return PERSONAL_NUMBER } if isNumberMatchingDesc(nationalNumber, metadata.GetPager()) { return PAGER } if isNumberMatchingDesc(nationalNumber, metadata.GetUan()) { return UAN } if isNumberMatchingDesc(nationalNumber, metadata.GetVoicemail()) { return VOICEMAIL } var isFixedLine = isNumberMatchingDesc( nationalNumber, metadata.GetFixedLine()) if isFixedLine { if metadata.GetSameMobileAndFixedLinePattern() { return FIXED_LINE_OR_MOBILE } else if isNumberMatchingDesc(nationalNumber, metadata.GetMobile()) { return FIXED_LINE_OR_MOBILE } return 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() && isNumberMatchingDesc(nationalNumber, metadata.GetMobile()) { return MOBILE } return UNKNOWN } // Returns the metadata for the given region code or nil if the region // code is invalid or unknown. func getMetadataForRegion(regionCode string) *PhoneMetadata { if !isValidRegionCode(regionCode) { return nil } val, _ := readFromRegionToMetadataMap(regionCode) return val } func getMetadataForNonGeographicalRegion(countryCallingCode int) *PhoneMetadata { _, ok := countryCodeToRegion[countryCallingCode] if !ok { return nil } val, _ := readFromCountryCodeToNonGeographicalMetadataMap(countryCallingCode) return val } func isNumberPossibleForDesc(nationalNumber string, numberDesc *PhoneNumberDesc) bool { // Check if any possible number lengths are present; if so, we use them to avoid checking the // validation pattern if they don't match. If they are absent, this means they match the general // description, which we have already checked before checking a specific number type. actualLength := int32(len(nationalNumber)) if len(numberDesc.PossibleLength) > 0 { found := false for _, l := range numberDesc.PossibleLength { if actualLength == l { found = true break } } if !found { return false } } possiblePattern := "^(?:" + numberDesc.GetNationalNumberPattern() + ")$" // Strictly match pat := regexFor(possiblePattern) return pat.MatchString(nationalNumber) } func isNumberMatchingDesc(nationalNumber string, numberDesc *PhoneNumberDesc) bool { patP := "^(?:" + numberDesc.GetNationalNumberPattern() + ")$" // Strictly match pat := regexFor(patP) return isNumberPossibleForDesc(nationalNumber, numberDesc) && pat.MatchString(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. func IsValidNumber(number *PhoneNumber) bool { var regionCode string = GetRegionCodeForNumber(number) return 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 calling code is // not the same as the country calling 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. // Warning: In most cases, you want to use IsValidNumber() instead. For // example, this method will mark numbers from British Crown dependencies // such as the Isle of Man as invalid for the region "GB" (United Kingdom), // since it has its own region code, "IM", which may be undesirable. func IsValidNumberForRegion(number *PhoneNumber, regionCode string) bool { var countryCode int = int(number.GetCountryCode()) var metadata *PhoneMetadata = getMetadataForRegionOrCallingCode(countryCode, regionCode) if metadata == nil || (REGION_CODE_FOR_NON_GEO_ENTITY != regionCode && countryCode != getCountryCodeForValidRegion(regionCode)) { // Either the region code was invalid, or the country calling // code for this number does not match that of the region code. return false } nationalSignificantNumber := GetNationalSignificantNumber(number) return getNumberTypeHelper(nationalSignificantNumber, metadata) != UNKNOWN } // Returns the region where a phone number is from. This could be used for // geocoding at the region level. func GetRegionCodeForNumber(number *PhoneNumber) string { var countryCode int = int(number.GetCountryCode()) var regions []string = countryCodeToRegion[countryCode] if len(regions) == 0 { return "" } if len(regions) == 1 { return regions[0] } return getRegionCodeForNumberFromRegionList(number, regions) } func getRegionCodeForNumberFromRegionList( number *PhoneNumber, regionCodes []string) string { var nationalNumber string = GetNationalSignificantNumber(number) for _, regionCode := range regionCodes { // If leadingDigits is present, use this. Otherwise, do // full validation. Metadata cannot be null because the // region codes come from the country calling code map. var metadata *PhoneMetadata = getMetadataForRegion(regionCode) if len(metadata.GetLeadingDigits()) > 0 { patP := "^(?:" + metadata.GetLeadingDigits() + ")" // Non capturing grouping to support OR'ed alternatives (e.g. 555|1[78]|2) pat := regexFor(patP) if pat.MatchString(nationalNumber) { return regionCode } } else if getNumberTypeHelper(nationalNumber, metadata) != UNKNOWN { return regionCode } } return "" } // Returns the region code that matches the specific country calling 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" region for this calling code will be returned. If the // countryCallingCode entered is valid but doesn't match a specific region // (such as in the case of non-geographical calling codes like 800) the // value "001" will be returned (corresponding to the value for World in // the UN M.49 schema). func GetRegionCodeForCountryCode(countryCallingCode int) string { var regionCodes []string = countryCodeToRegion[countryCallingCode] if len(regionCodes) == 0 { return UNKNOWN_REGION } return regionCodes[0] } // Returns a list with the region codes that match the specific country // calling code. For non-geographical country calling codes, the region // code 001 is returned. Also, in the case of no region code being found, // an empty list is returned. func GetRegionCodesForCountryCode(countryCallingCode int) []string { var regionCodes []string = countryCodeToRegion[countryCallingCode] return regionCodes } // Returns the country calling code for a specific region. For example, this // would be 1 for the United States, and 64 for New Zealand. func GetCountryCodeForRegion(regionCode string) int { if !isValidRegionCode(regionCode) { return 0 } return getCountryCodeForValidRegion(regionCode) } // Returns the country calling code for a specific region. For example, // this would be 1 for the United States, and 64 for New Zealand. Assumes // the region is already valid. func getCountryCodeForValidRegion(regionCode string) int { var metadata *PhoneMetadata = getMetadataForRegion(regionCode) return int(metadata.GetCountryCode()) } // Returns the national dialling prefix for a specific region. For example, // this would be 1 for the United States, and 0 for New Zealand. Set // stripNonDigits to true to strip symbols like "~" (which indicates a // wait for a dialling tone) from the prefix returned. If no national prefix // is present, we return null. // // Warning: Do not use this method for do-your-own formatting - for some // regions, the national dialling prefix is used only for certain types // of numbers. Use the library's formatting functions to prefix the // national prefix when required. func GetNddPrefixForRegion(regionCode string, stripNonDigits bool) string { var metadata *PhoneMetadata = getMetadataForRegion(regionCode) if metadata == nil { return "" } var nationalPrefix string = metadata.GetNationalPrefix() // If no national prefix was found, we return an empty string. if len(nationalPrefix) == 0 { return "" } if stripNonDigits { // Note: if any other non-numeric symbols are ever used in // national prefixes, these would have to be removed here as well. nationalPrefix = strings.Replace(nationalPrefix, "~", "", -1) } return nationalPrefix } // Checks if this is a region under the North American Numbering Plan // Administration (NANPA). func IsNANPACountry(regionCode string) bool { _, ok := readFromNanpaRegions(regionCode) return ok } // Checks if the number is a valid vanity (alpha) number such as 800 // MICROSOFT. A valid vanity number will start with at least 3 digits and // will have three or more alpha characters. This does not do // region-specific checks - to work out if this number is actually valid // for a region, it should be parsed and methods such as // IsPossibleNumberWithReason() and IsValidNumber() should be used. func IsAlphaNumber(number string) bool { if !isViablePhoneNumber(number) { // Number is too short, or doesn't match the basic phone // number pattern. return false } strippedNumber := NewBuilderString(number) maybeStripExtension(strippedNumber) return VALID_ALPHA_PHONE_PATTERN.MatchString(strippedNumber.String()) } // Convenience wrapper around IsPossibleNumberWithReason(). Instead of // returning the reason for failure, this method returns a boolean value. func IsPossibleNumber(number *PhoneNumber) bool { possible := IsPossibleNumberWithReason(number) return possible == IS_POSSIBLE || possible == IS_POSSIBLE_LOCAL_ONLY } func descHasPossibleNumberData(desc *PhoneNumberDesc) bool { return len(desc.PossibleLength) > 0 && desc.PossibleLength[0] != -1 } func mergeLengths(l1 []int32, l2 []int32) []int32 { merged := make([]int32, len(l1)+len(l2)) l1i, l2i := 0, 0 for i := 0; i < len(merged); i++ { if l1i < len(l1) { if l2i < len(l2) { if l1[l1i] <= l2[l2i] { merged[i] = l1[l1i] l1i++ } else { merged[i] = l2[l2i] l2i++ } } else { merged[i] = l1[l1i] l1i++ } } else { merged[i] = l2[l2i] l2i++ } } return merged } // Helper method to check a number against possible lengths for this number type, and determine // whether it matches, or is too short or too long. func testNumberLength(number string, metadata *PhoneMetadata, numberType PhoneNumberType) ValidationResult { desc := getNumberDescByType(metadata, numberType) // There should always be "possibleLengths" set for every element. This is declared in the XML // schema which is verified by PhoneNumberMetadataSchemaTest. // For size efficiency, where a sub-description (e.g. fixed-line) has the same possibleLengths // as the parent, this is missing, so we fall back to the general desc (where no numbers of the // type exist at all, there is one possible length (-1) which is guaranteed not to match the // length of any real phone number). possibleLengths := desc.PossibleLength if len(possibleLengths) == 0 { possibleLengths = metadata.GeneralDesc.PossibleLength } localLengths := desc.PossibleLengthLocalOnly if numberType == FIXED_LINE_OR_MOBILE { if !descHasPossibleNumberData(getNumberDescByType(metadata, FIXED_LINE)) { // The rare case has been encountered where no fixedLine data is available (true for some // non-geographical entities), so we just check mobile. return testNumberLength(number, metadata, MOBILE) } else { mobileDesc := getNumberDescByType(metadata, MOBILE) if descHasPossibleNumberData(mobileDesc) { // Note that when adding the possible lengths from mobile, we have to again check they // aren't empty since if they are this indicates they are the same as the general desc and // should be obtained from there. mobileLengths := mobileDesc.PossibleLength if len(mobileLengths) == 0 { mobileLengths = metadata.GeneralDesc.PossibleLength } possibleLengths = mergeLengths(possibleLengths, mobileLengths) if len(localLengths) == 0 { localLengths = mobileDesc.PossibleLengthLocalOnly } else { localLengths = mergeLengths(localLengths, mobileDesc.PossibleLengthLocalOnly) } } } } // If the type is not supported at all (indicated by the possible lengths containing -1 at this // point) we return invalid length. if possibleLengths[0] == -1 { return INVALID_LENGTH } actualLength := int32(len(number)) // This is safe because there is never an overlap beween the possible lengths and the local-only // lengths; this is checked at build time. for _, l := range localLengths { if l == actualLength { return IS_POSSIBLE_LOCAL_ONLY } } minimumLength := possibleLengths[0] if minimumLength == actualLength { return IS_POSSIBLE } else if minimumLength > actualLength { return TOO_SHORT } else if possibleLengths[len(possibleLengths)-1] < actualLength { return TOO_LONG } // We skip the first element; we've already checked it. for _, l := range possibleLengths[1:] { if l == actualLength { return IS_POSSIBLE } } return INVALID_LENGTH } // Check whether a phone number is a possible number. It provides a more // lenient check than IsValidNumber() in the following sense: // // - It only checks the length of phone numbers. In particular, it // doesn't check starting digits of the number. // - 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 // region. Therefore, it is much faster than isValidNumber. // - For fixed line numbers, many regions 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. func IsPossibleNumberWithReason(number *PhoneNumber) ValidationResult { nationalNumber := GetNationalSignificantNumber(number) countryCode := int(number.GetCountryCode()) // 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 regions within // those plans. if !hasValidCountryCallingCode(countryCode) { return INVALID_COUNTRY_CODE } regionCode := GetRegionCodeForCountryCode(countryCode) // Metadata cannot be null because the country calling code is valid. var metadata *PhoneMetadata = getMetadataForRegionOrCallingCode(countryCode, regionCode) var generalNumDesc *PhoneNumberDesc = metadata.GetGeneralDesc() // Handling case of numbers with no metadata. if len(generalNumDesc.GetNationalNumberPattern()) == 0 { numberLength := len(nationalNumber) if numberLength < MIN_LENGTH_FOR_NSN { return TOO_SHORT } else if numberLength > MAX_LENGTH_FOR_NSN { return TOO_LONG } else { return IS_POSSIBLE } } return testNumberLength(nationalNumber, metadata, UNKNOWN) } // Attempts to extract a valid number from a phone number that is too long // to be valid, and resets the PhoneNumber object passed in to that valid // version. If no valid number could be extracted, the PhoneNumber object // passed in will not be modified. func TruncateTooLongNumber(number *PhoneNumber) bool { if IsValidNumber(number) { return true } numberCopy := &PhoneNumber{} proto.Merge(numberCopy, number) nationalNumber := number.GetNationalNumber() nationalNumber /= 10 numberCopy.NationalNumber = proto.Uint64(nationalNumber) if IsPossibleNumberWithReason(numberCopy) == TOO_SHORT || nationalNumber == 0 { return false } for !IsValidNumber(numberCopy) { nationalNumber /= 10 numberCopy.NationalNumber = proto.Uint64(nationalNumber) if IsPossibleNumberWithReason(numberCopy) == TOO_SHORT || nationalNumber == 0 { return false } } number.NationalNumber = proto.Uint64(nationalNumber) return true } // Gets an AsYouTypeFormatter for the specific region. // TODO(ttacon): uncomment once we do asyoutypeformatter.go // public AsYouTypeFormatter getAsYouTypeFormatter(String regionCode) { // return new AsYouTypeFormatter(regionCode); // } // Extracts country calling 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 calling code, and leaves nationalNumber // unmodified. func extractCountryCode(fullNumber, nationalNumber *Builder) int { fullNumBytes := fullNumber.Bytes() if len(fullNumBytes) == 0 || fullNumBytes[0] == '0' { // Country codes do not begin with a '0'. return 0 } var ( potentialCountryCode int numberLength = len(fullNumBytes) ) for i := 1; i <= MAX_LENGTH_COUNTRY_CODE && i <= numberLength; i++ { potentialCountryCode, _ = strconv.Atoi(string(fullNumBytes[0:i])) if _, ok := countryCodeToRegion[potentialCountryCode]; ok { nationalNumber.Write(fullNumBytes[i:]) return potentialCountryCode } } return 0 } var ErrTooShortAfterIDD = errors.New("phone number had an IDD, but " + "after this was not long enough to be a viable phone number") // Tries to extract a country calling code from a number. This method will // return zero if no country calling code is considered to be present. // Country calling codes are extracted in the following ways: // // - by stripping the international dialing prefix of the region 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 calling 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 calling code of this region, validation will be reattempted // after stripping this country calling code. If this number is considered a // possible number, then the first digits will be considered the country // calling code and removed as such. // // It will throw a NumberParseException if the number starts with a '+' but // the country calling code supplied after this does not match that of any // known region. func maybeExtractCountryCode( number string, defaultRegionMetadata *PhoneMetadata, nationalNumber *Builder, keepRawInput bool, phoneNumber *PhoneNumber) (int, error) { if len(number) == 0 { return 0, nil } fullNumber := NewBuilderString(number) // Set the default prefix to be something that will never match. possibleCountryIddPrefix := "NonMatch" if defaultRegionMetadata != nil { possibleCountryIddPrefix = defaultRegionMetadata.GetInternationalPrefix() } countryCodeSource := maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix) if keepRawInput { phoneNumber.CountryCodeSource = &countryCodeSource } if countryCodeSource != PhoneNumber_FROM_DEFAULT_COUNTRY { if len(fullNumber.String()) <= MIN_LENGTH_FOR_NSN { return 0, ErrTooShortAfterIDD } potentialCountryCode := extractCountryCode(fullNumber, nationalNumber) if potentialCountryCode != 0 { phoneNumber.CountryCode = proto.Int32(int32(potentialCountryCode)) return potentialCountryCode, nil } // If this fails, they must be using a strange country calling code // that we don't recognize, or that doesn't exist. return 0, ErrInvalidCountryCode } else if defaultRegionMetadata != nil { // Check to see if the number starts with the country calling code // for the default region. If so, we remove the country calling // code, and do some checks on the validity of the number before // and after. defaultCountryCode := int(defaultRegionMetadata.GetCountryCode()) defaultCountryCodeString := strconv.Itoa(defaultCountryCode) normalizedNumber := fullNumber.String() if strings.HasPrefix(normalizedNumber, defaultCountryCodeString) { var ( potentialNationalNumber = NewBuilderString( normalizedNumber[len(defaultCountryCodeString):]) generalDesc = defaultRegionMetadata.GetGeneralDesc() patP = `^(?:` + generalDesc.GetNationalNumberPattern() + `)$` // Strictly match validNumberPattern = regexFor(patP) ) maybeStripNationalPrefixAndCarrierCode( potentialNationalNumber, defaultRegionMetadata, NewBuilder(nil) /* Don't need the carrier code */) // If the number was not valid before but is valid now, or // if it was too long before, we consider the number with // the country calling code stripped to be a better result and // keep that instead. fullValid := validNumberPattern.MatchString(fullNumber.String()) nationalValid := validNumberPattern.MatchString(potentialNationalNumber.String()) lengthValid := testNumberLength(fullNumber.String(), defaultRegionMetadata, UNKNOWN) if (!fullValid && nationalValid) || lengthValid == TOO_LONG { nationalNumber.Write(potentialNationalNumber.Bytes()) if keepRawInput { val := PhoneNumber_FROM_NUMBER_WITHOUT_PLUS_SIGN phoneNumber.CountryCodeSource = &val } phoneNumber.CountryCode = proto.Int32(int32(defaultCountryCode)) return defaultCountryCode, nil } } } // No country calling code present. phoneNumber.CountryCode = proto.Int32(0) return 0, nil } // Strips the IDD from the start of the number if present. Helper function // used by maybeStripInternationalPrefixAndNormalize. func parsePrefixAsIdd(iddPattern *regexp.Regexp, number *Builder) bool { numStr := number.String() ind := iddPattern.FindStringIndex(numStr) if len(ind) == 0 || ind[0] != 0 { return false } matchEnd := ind[1] // ind is a two element slice // Only strip this if the first digit after the match is not // a 0, since country calling codes cannot begin with 0. find := CAPTURING_DIGIT_PATTERN.FindAllString(numStr[matchEnd:], -1) if len(find) > 0 { if NormalizeDigitsOnly(find[0]) == "0" { return false } } numBytes := []byte(numStr) number.ResetWith(numBytes[matchEnd:]) return true } // 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. func maybeStripInternationalPrefixAndNormalize( number *Builder, possibleIddPrefix string) PhoneNumber_CountryCodeSource { numBytes := number.Bytes() if len(numBytes) == 0 { return PhoneNumber_FROM_DEFAULT_COUNTRY } // Check to see if the number begins with one or more plus signs. ind := PLUS_CHARS_PATTERN.FindIndex(numBytes) // Return is an int pair [start,end] if len(ind) > 0 && ind[0] == 0 { // Strictly match from string start number.ResetWith(numBytes[ind[1]:]) // Can now normalize the rest of the number since we've consumed // the "+" sign at the start. number.ResetWithString(normalize(number.String())) return PhoneNumber_FROM_NUMBER_WITH_PLUS_SIGN } // Attempt to parse the first digits as an international prefix. iddPattern := regexFor(possibleIddPrefix) number.ResetWithString(normalize(string(numBytes))) if parsePrefixAsIdd(iddPattern, number) { return PhoneNumber_FROM_NUMBER_WITH_IDD } return PhoneNumber_FROM_DEFAULT_COUNTRY } // Strips any national prefix (such as 0, 1) present in the number provided. // @VisibleForTesting func maybeStripNationalPrefixAndCarrierCode( number *Builder, metadata *PhoneMetadata, carrierCode *Builder) bool { numberLength := len(number.String()) possibleNationalPrefix := metadata.GetNationalPrefixForParsing() if numberLength == 0 || len(possibleNationalPrefix) == 0 { // Early return for numbers of zero length. return false } possibleNationalPrefix = "^(?:" + possibleNationalPrefix + ")" // Strictly match from string start // Attempt to parse the first digits as a national prefix. prefixMatcher := regexFor(possibleNationalPrefix) if prefixMatcher.MatchString(number.String()) { natRulePattern := "^(?:" + metadata.GetGeneralDesc().GetNationalNumberPattern() + ")$" // Strictly match nationalNumberRule := regexFor(natRulePattern) // Check if the original number is viable. isViableOriginalNumber := nationalNumberRule.Match(number.Bytes()) // prefixMatcher.group(numOfGroups) == null implies nothing was // captured by the capturing groups in possibleNationalPrefix; // therefore, no transformation is necessary, and we just // remove the national prefix. groups := prefixMatcher.FindSubmatchIndex(number.Bytes()) numOfGroups := len(groups)/2 - 1 // groups is a list of index pairs, idx0,idx1 defines the whole match, idx2+ submatches. // Subtract one to ignore group(0) in count transformRule := metadata.GetNationalPrefixTransformRule() if len(transformRule) == 0 || groups[numOfGroups*2] < 0 { // Negative idx means subgroup did not match // If the original number was viable, and the resultant number // is not, we return. if isViableOriginalNumber && !nationalNumberRule.MatchString( number.String()[groups[1]:]) { // groups[1] == last match idx return false } if len(carrierCode.Bytes()) != 0 && numOfGroups > 0 && groups[numOfGroups*2] > 0 { // Negative idx means subgroup did not match carrierCode.Write(number.Bytes()[groups[numOfGroups*2]:groups[numOfGroups*2+1]]) } number.ResetWith(number.Bytes()[groups[1]:]) return true } else { // Check that the resultant number is still viable. If not, // return. Check this by copying the string buffer and // making the transformation on the copy first. numString := number.String() transformedNumBytes := []byte(prefixMatcher.ReplaceAllString(numString, transformRule)) if isViableOriginalNumber && !nationalNumberRule.Match(transformedNumBytes) { return false } if len(carrierCode.Bytes()) != 0 && numOfGroups > 1 && groups[2] != -1 { // Check group(1) got a submatch carrC := numString[groups[2]:groups[3]] // group(1) idxs carrierCode.WriteString(carrC) } number.ResetWith(transformedNumBytes) return true } } return false } // MaybeSeparateExtensionFromPhone will extract 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 along // with the proceeding phone number. The phone number will maintain its // original formatting including alpha characters. func MaybeSeparateExtensionFromPhone(rawPhone string) (phoneNumber string, extension string) { phoneNumber, extWithSeparator := splitAtExtensionSeparator(rawPhone) if !isViablePhoneNumber(phoneNumber) || extWithSeparator == "" { return rawPhone, "" } extension = removeLeadingExtensionSeparator(extWithSeparator) return phoneNumber, extension } func splitAtExtensionSeparator(rawPhone string) (phoneNumber string, extWithSeparator string) { ind := EXTN_PATTERN.FindStringIndex(rawPhone) if len(ind) == 0 { return rawPhone, "" } return rawPhone[0:ind[0]], rawPhone[ind[0]:] } func removeLeadingExtensionSeparator(extWithSeparator string) string { matches := EXTN_PATTERN.FindStringSubmatch(extWithSeparator) for _, extension := range matches[1:] { if len(extension) > 0 { return extension } } return "" } // 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. // @VisibleForTesting func maybeStripExtension(number *Builder) string { // If we find a potential extension, and the number preceding this is // a viable number, we assume it is an extension. numStr := number.String() ind := EXTN_PATTERN.FindStringIndex(numStr) if len(ind) > 0 && isViablePhoneNumber(numStr[0:ind[0]]) { // The numbers are captured into groups in the regular expression. for _, extension := range EXTN_PATTERN.FindStringSubmatch(numStr)[1:] { if len(extension) == 0 { continue } // We go through the capturing groups until we find one // that captured some digits. If none did, then we will // return the empty string. number.ResetWithString(numStr[0:ind[0]]) return extension } } return "" } // Checks to see that the region code used is valid, or if it is not valid, // that the number to parse starts with a + symbol so that we can attempt // to infer the region from the number. Returns false if it cannot use the // region provided and the region cannot be inferred. func checkRegionForParsing(numberToParse, defaultRegion string) bool { if !isValidRegionCode(defaultRegion) { // If the number is null or empty, we can't infer the region. if len(numberToParse) == 0 || !PLUS_CHARS_PATTERN.MatchString(numberToParse) { return false } } return true } // Parses a string and returns it in proto buffer format. This method will // throw a NumberParseException 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 region is not performed. This can be // done separately with IsValidNumber(). func Parse(numberToParse, defaultRegion string) (*PhoneNumber, error) { var phoneNumber *PhoneNumber = &PhoneNumber{} err := ParseToNumber(numberToParse, defaultRegion, phoneNumber) return phoneNumber, err } // Same as Parse(string, string), but accepts mutable PhoneNumber as a // parameter to decrease object creation when invoked many times. func ParseToNumber(numberToParse, defaultRegion string, phoneNumber *PhoneNumber) error { return parseHelper(numberToParse, defaultRegion, false, true, 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. func ParseAndKeepRawInput( numberToParse, defaultRegion string) (*PhoneNumber, error) { var phoneNumber *PhoneNumber = &PhoneNumber{} return phoneNumber, ParseAndKeepRawInputToNumber( numberToParse, defaultRegion, phoneNumber) } // Same as ParseAndKeepRawInput(String, String), but accepts a mutable // PhoneNumber as a parameter to decrease object creation when invoked many // times. func ParseAndKeepRawInputToNumber( numberToParse, defaultRegion string, phoneNumber *PhoneNumber) error { return parseHelper(numberToParse, defaultRegion, true, true, phoneNumber) } // Returns an iterable over all PhoneNumberMatch PhoneNumberMatches in text. // This is a shortcut for findNumbers(CharSequence, String, Leniency, long) // getMatcher(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE)}. // public Iterable<PhoneNumberMatch> findNumbers(CharSequence text, String defaultRegion) { // return findNumbers(text, defaultRegion, Leniency.VALID, Long.MAX_VALUE); // } // Returns an iterable over all PhoneNumberMatch PhoneNumberMatches in text. // public Iterable<PhoneNumberMatch> findNumbers( // final CharSequence text, final String defaultRegion, final Leniency leniency, // final long maxTries) { // // return new Iterable<PhoneNumberMatch>() { // public Iterator<PhoneNumberMatch> iterator() { // return new PhoneNumberMatcher( // PhoneNumberUtil.this, text, defaultRegion, leniency, maxTries); // } // }; // } // A helper function to set the values related to leading zeros in a // PhoneNumber. func setItalianLeadingZerosForPhoneNumber( nationalNum string, phoneNumber *PhoneNumber) { if len(nationalNum) < 2 || nationalNum[0] != '0' { phoneNumber.ItalianLeadingZero = nil return } phoneNumber.ItalianLeadingZero = proto.Bool(true) numLeadZeros := 1 // Note that if the national number is all "0"s, the last "0" // is not counted as a leading zero. for numLeadZeros < len(nationalNum)-1 && nationalNum[numLeadZeros] == '0' { numLeadZeros++ } if numLeadZeros != 1 { phoneNumber.NumberOfLeadingZeros = proto.Int32(int32(numLeadZeros)) } } var ( ErrInvalidCountryCode = errors.New("invalid country code") ErrNotANumber = errors.New("the phone number supplied is not a number") ErrTooShortNSN = errors.New("the string supplied is too short to be a phone number") ) // 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 region to be null, for use by IsNumberMatch(). checkRegion should // be set to false if it is permitted for the default region to be null or // unknown ("ZZ"). func parseHelper( numberToParse, defaultRegion string, keepRawInput, checkRegion bool, phoneNumber *PhoneNumber) error { if len(numberToParse) == 0 { return ErrNotANumber } else if len(numberToParse) > MAX_INPUT_STRING_LENGTH { return ErrNumTooLong } nationalNumber := NewBuilder(nil) err := buildNationalNumberForParsing(numberToParse, nationalNumber) if err != nil { return err } if !isViablePhoneNumber(nationalNumber.String()) { return ErrNotANumber } // Check the region supplied is valid, or that the extracted number // starts with some sort of + sign so the number's region can be determined. if checkRegion && !checkRegionForParsing(nationalNumber.String(), defaultRegion) { return ErrInvalidCountryCode } if keepRawInput { phoneNumber.RawInput = proto.String(numberToParse) } // Attempt to parse extension first, since it doesn't require // region-specific data and we want to have the non-normalised // number here. extension := maybeStripExtension(nationalNumber) if len(extension) > 0 { phoneNumber.Extension = proto.String(extension) } var regionMetadata *PhoneMetadata = getMetadataForRegion(defaultRegion) // Check to see if the number is given in international format so we // know whether this number is from the default region or not. normalizedNationalNumber := NewBuilder(nil) // TODO: This method should really just take in the string buffer that // has already been created, and just remove the prefix, rather than // taking in a string and then outputting a string buffer. countryCode, err := maybeExtractCountryCode( nationalNumber.String(), regionMetadata, normalizedNationalNumber, keepRawInput, phoneNumber) if err != nil { // There might be a plus at the beginning inds := PLUS_CHARS_PATTERN.FindStringIndex(nationalNumber.String()) if err == ErrInvalidCountryCode && len(inds) > 0 { // Strip the plus-char, and try again. countryCode, err = maybeExtractCountryCode( nationalNumber.String()[inds[1]:], regionMetadata, normalizedNationalNumber, keepRawInput, phoneNumber) if err != nil { return err } else if countryCode == 0 { return ErrInvalidCountryCode } } else { return err } } if countryCode != 0 { phoneNumberRegion := GetRegionCodeForCountryCode(countryCode) if phoneNumberRegion != defaultRegion { // Metadata cannot be null because the country calling // code is valid. regionMetadata = getMetadataForRegionOrCallingCode( countryCode, phoneNumberRegion) } } else { // If no extracted country calling code, use the region supplied // instead. The national number is just the normalized version of // the number we were given to parse. normalizedNationalNumber.WriteString(normalize(nationalNumber.String())) if len(defaultRegion) != 0 { countryCode = int(regionMetadata.GetCountryCode()) phoneNumber.CountryCode = proto.Int32(int32(countryCode)) } else if keepRawInput { phoneNumber.CountryCodeSource = nil } } if len(normalizedNationalNumber.String()) < MIN_LENGTH_FOR_NSN { return ErrTooShortNSN } if regionMetadata != nil { carrierCode := NewBuilder(nil) bufferCopy := make([]byte, normalizedNationalNumber.Len()) copy(bufferCopy, normalizedNationalNumber.Bytes()) potentialNationalNumber := NewBuilder(bufferCopy) maybeStripNationalPrefixAndCarrierCode( potentialNationalNumber, regionMetadata, carrierCode) // We require that the NSN remaining after stripping the national // prefix and carrier code be of a possible length for the region. // Otherwise, we don't do the stripping, since the original number // could be a valid short number. validationResult := testNumberLength(potentialNationalNumber.String(), regionMetadata, UNKNOWN) if validationResult != TOO_SHORT && validationResult != IS_POSSIBLE_LOCAL_ONLY && validationResult != INVALID_LENGTH { normalizedNationalNumber = potentialNationalNumber if keepRawInput { phoneNumber.PreferredDomesticCarrierCode = proto.String(carrierCode.String()) } } } lengthOfNationalNumber := len(normalizedNationalNumber.String()) if lengthOfNationalNumber < MIN_LENGTH_FOR_NSN { return ErrTooShortNSN } if lengthOfNationalNumber > MAX_LENGTH_FOR_NSN { return ErrNumTooLong } setItalianLeadingZerosForPhoneNumber( normalizedNationalNumber.String(), phoneNumber) val, _ := strconv.ParseUint(normalizedNationalNumber.String(), 10, 64) phoneNumber.NationalNumber = proto.Uint64(val) return nil } var ErrNumTooLong = errors.New("the string supplied is too long to be a phone number") // Extracts the value of the phone-context parameter of numberToExtractFrom where the index of // ";phone-context=" is the parameter indexOfPhoneContext, following the syntax defined in // RFC3966. func extractPhoneContext(numberToExtractFrom string, indexOfPhoneContext int) string { // If no phone-context parameter is present if indexOfPhoneContext == -1 { return "" } phoneContextStart := indexOfPhoneContext + len(RFC3966_PHONE_CONTEXT) // If phone-context parameter is empty if phoneContextStart >= len(numberToExtractFrom) { return "" } // find end of this phone-context (go doesn't have a indexOf(s, after)) phoneContextEnd := strings.IndexRune(numberToExtractFrom[phoneContextStart:], ';') if phoneContextEnd != -1 { phoneContextEnd += phoneContextStart } // If phone-context is not the last parameter if phoneContextEnd != -1 { return numberToExtractFrom[phoneContextStart:phoneContextEnd] } else { return numberToExtractFrom[phoneContextStart:] } } // Returns whether the value of phoneContext follows the syntax defined in RFC3966. func isPhoneContextValid(phoneContext string) bool { if len(phoneContext) == 0 { return false } // Does phone-context value match pattern of global-number-digits or domainname return RFC3966_GLOBAL_NUMBER_DIGITS_PATTERN.MatchString(phoneContext) || RFC3966_DOMAINNAME_PATTERN.MatchString(phoneContext) } // Converts numberToParse to a form that we can parse and write it to // nationalNumber if it is written in RFC3966; otherwise extract a possible // number out of it and write to nationalNumber. func buildNationalNumberForParsing( numberToParse string, nationalNumber *Builder) error { indexOfPhoneContext := strings.Index(numberToParse, RFC3966_PHONE_CONTEXT) phoneContext := extractPhoneContext(numberToParse, indexOfPhoneContext) if indexOfPhoneContext >= 0 && !isPhoneContextValid(phoneContext) { return ErrNotANumber } if indexOfPhoneContext > 0 { // If the phone context contains a phone number prefix, we need to capture it, whereas domains // will be ignored. if phoneContext[0] == PLUS_SIGN { // Additional parameters might follow the phone context. If so, we will remove them here // because the parameters after phone context are not important for parsing the phone // number. nationalNumber.WriteString(phoneContext) } // Now append everything between the "tel:" prefix and the phone-context. This should include // the national number, an optional extension or isdn-subaddress component. Note we also // handle the case when "tel:" is missing, as we have seen in some of the phone number inputs. // In that case, we append everything from the beginning. indexOfRfc3966Prefix := strings.Index(numberToParse, RFC3966_PREFIX) indexOfNationalNumber := 0 if indexOfRfc3966Prefix >= 0 { indexOfNationalNumber = indexOfRfc3966Prefix + len(RFC3966_PREFIX) } nationalNumber.WriteString(numberToParse[indexOfNationalNumber:indexOfPhoneContext]) } else { // Extract a possible number from the string passed in (this // strips leading characters that could not be the start of a // phone number.) nationalNumber.WriteString(extractPossibleNumber(numberToParse)) } // Delete the isdn-subaddress and everything after it if it is present. // Note extension won't appear at the same time with isdn-subaddress // according to paragraph 5.3 of the RFC3966 spec, indexOfIsdn := strings.Index(nationalNumber.String(), RFC3966_ISDN_SUBADDRESS) if indexOfIsdn > 0 { natNumBytes := nationalNumber.Bytes() nationalNumber.ResetWith(natNumBytes[:indexOfIsdn]) } // If both phone context and isdn-subaddress are absent but other // parameters are present, the parameters are left in nationalNumber. // This is because we are concerned about deleting content from a // potential number string when there is no strong evidence that the // number is actually written in RFC3966. return nil } // 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 region specified, and the NSNs // and extensions are the same. // Returns SHORT_NSN_MATCH if either or both has no region specified, or the // region 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. func IsNumberMatchWithNumbers(firstNumberIn, secondNumberIn *PhoneNumber) MatchType { // Make copies of the phone number so that the numbers passed in are not edited. var firstNumber, secondNumber *PhoneNumber firstNumber = &PhoneNumber{} secondNumber = &PhoneNumber{} proto.Merge(firstNumber, firstNumberIn) proto.Merge(secondNumber, secondNumberIn) // First clear raw_input, country_code_source and // preferred_domestic_carrier_code fields and any empty-string // extensions so that we can use the proto-buffer equality method. firstNumber.RawInput = nil firstNumber.CountryCodeSource = nil firstNumber.PreferredDomesticCarrierCode = nil secondNumber.RawInput = nil secondNumber.CountryCodeSource = nil secondNumber.PreferredDomesticCarrierCode = nil firstNumExt := firstNumber.GetExtension() secondNumExt := secondNumber.GetExtension() // NOTE(ttacon): don't think we need this in go land... if len(firstNumExt) == 0 { firstNumber.Extension = nil } if len(secondNumExt) == 0 { secondNumber.Extension = nil } // Early exit if both had extensions and these are different. if len(firstNumExt) > 0 && len(secondNumExt) > 0 && firstNumExt != secondNumExt { return NO_MATCH } var ( firstNumberCountryCode = firstNumber.GetCountryCode() secondNumberCountryCode = secondNumber.GetCountryCode() ) // Both had country_code specified. if firstNumberCountryCode != 0 && secondNumberCountryCode != 0 { // TODO(ttacon): remove when make gen-equals if reflect.DeepEqual(firstNumber, secondNumber) { return EXACT_MATCH } else if firstNumberCountryCode == secondNumberCountryCode && 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 SHORT_NSN_MATCH } // This is not a match. return NO_MATCH } // Checks cases where one or both country_code fields were not // specified. To make equality checks easier, we first set the // country_code fields to be equal. firstNumber.CountryCode = proto.Int32(secondNumberCountryCode) // If all else was the same, then this is an NSN_MATCH. // TODO(ttacon): remove when make gen-equals if reflect.DeepEqual(firstNumber, secondNumber) { return NSN_MATCH } if isNationalNumberSuffixOfTheOther(firstNumber, secondNumber) { return SHORT_NSN_MATCH } return NO_MATCH } // Returns true when one national number is the suffix of the other or both // are the same. func isNationalNumberSuffixOfTheOther(firstNumber, secondNumber *PhoneNumber) bool { var ( firstNumberNationalNumber = strconv.FormatUint( firstNumber.GetNationalNumber(), 10) secondNumberNationalNumber = strconv.FormatUint( secondNumber.GetNationalNumber(), 10) ) // Note that endsWith returns true if the numbers are equal. return strings.HasSuffix(firstNumberNationalNumber, secondNumberNationalNumber) || strings.HasSuffix(secondNumberNationalNumber, firstNumberNationalNumber) } // Takes two phone numbers as strings and compares them for equality. This is // a convenience wrapper for IsNumberMatch(PhoneNumber, PhoneNumber). No // default region is known. func IsNumberMatch(firstNumber, secondNumber string) MatchType { firstNumberAsProto, err := Parse(firstNumber, UNKNOWN_REGION) if err == nil { return IsNumberMatchWithOneNumber(firstNumberAsProto, secondNumber) } else if err != ErrInvalidCountryCode { return NOT_A_NUMBER } secondNumberAsProto, err := Parse(secondNumber, UNKNOWN_REGION) if err == nil { return IsNumberMatchWithOneNumber(secondNumberAsProto, firstNumber) } else if err != ErrInvalidCountryCode { return NOT_A_NUMBER } var firstNumberProto, secondNumberProto PhoneNumber err = parseHelper(firstNumber, "", false, false, &firstNumberProto) if err != nil { return NOT_A_NUMBER } err = parseHelper(secondNumber, "", false, false, &secondNumberProto) if err != nil { return NOT_A_NUMBER } return IsNumberMatchWithNumbers(&firstNumberProto, &secondNumberProto) } // Takes two phone numbers and compares them for equality. This is a // convenience wrapper for IsNumberMatch(PhoneNumber, PhoneNumber). No // default region is known. func IsNumberMatchWithOneNumber( firstNumber *PhoneNumber, secondNumber string) MatchType { // First see if the second number has an implicit country calling // code, by attempting to parse it. secondNumberAsProto, err := Parse(secondNumber, UNKNOWN_REGION) if err == nil { return IsNumberMatchWithNumbers(firstNumber, secondNumberAsProto) } if err != ErrInvalidCountryCode { return NOT_A_NUMBER } // The second number has no country calling code. EXACT_MATCH is no // longer possible. We parse it as if the region was the same as that // for the first number, and if EXACT_MATCH is returned, we replace // this with NSN_MATCH. firstNumberRegion := GetRegionCodeForCountryCode(int(firstNumber.GetCountryCode())) if firstNumberRegion != UNKNOWN_REGION { secondNumberWithFirstNumberRegion, err := Parse(secondNumber, firstNumberRegion) if err != nil { return NOT_A_NUMBER } match := IsNumberMatchWithNumbers( firstNumber, secondNumberWithFirstNumberRegion) if match == EXACT_MATCH { return NSN_MATCH } return match } else { // If the first number didn't have a valid country calling // code, then we parse the second number without one as well. var secondNumberProto *PhoneNumber err := parseHelper(secondNumber, "", false, false, secondNumberProto) if err != nil { return NOT_A_NUMBER } return IsNumberMatchWithNumbers(firstNumber, secondNumberProto) } } // Returns true if the number can be dialled from outside the region, or // unknown. If the number can only be dialled from within the region, // returns false. Does not check the number is a valid number. Note that, // at the moment, this method does not handle short numbers. // TODO: Make this method public when we have enough metadata to make it worthwhile. func canBeInternationallyDialled(number *PhoneNumber) bool { metadata := getMetadataForRegion(GetRegionCodeForNumber(number)) if metadata == nil { // Note numbers belonging to non-geographical entities // (e.g. +800 numbers) are always internationally diallable, // and will be caught here. return true } nationalSignificantNumber := GetNationalSignificantNumber(number) return !isNumberMatchingDesc( nationalSignificantNumber, metadata.GetNoInternationalDialling()) } // Returns true if the supplied region supports mobile number portability. // Returns false for invalid, unknown or regions that don't support mobile // number portability. func IsMobileNumberPortableRegion(regionCode string) bool { metadata := getMetadataForRegion(regionCode) if metadata == nil { return false } return metadata.GetMobileNumberPortableRegion() } func init() { // load our regions regionMap, err := loadIntStringArrayMap(gen.RegionData) if err != nil { panic(err) } countryCodeToRegion = regionMap.Map // then our metadata err = loadMetadataFromFile("US", 1) if err != nil { panic(err) } for eKey, regionCodes := range countryCodeToRegion { // We can assume that if the county calling code maps to the // non-geo entity region code then that's the only region code // it maps to. if len(regionCodes) == 1 && REGION_CODE_FOR_NON_GEO_ENTITY == regionCodes[0] { // This is the subset of all country codes that map to the // non-geo entity region code. countryCodesForNonGeographicalRegion[eKey] = true } else { // The supported regions set does not include the "001" // non-geo entity region code. for _, val := range regionCodes { supportedRegions[val] = true } } supportedCallingCodes[eKey] = true } // If the non-geo entity still got added to the set of supported // regions it must be because there are entries that list the non-geo // entity alongside normal regions (which is wrong). If we discover // this, remove the non-geo entity from the set of supported regions // and log (or not log). delete(supportedRegions, REGION_CODE_FOR_NON_GEO_ENTITY) for _, val := range countryCodeToRegion[NANPA_COUNTRY_CODE] { writeToNanpaRegions(val, struct{}{}) } // Create our sync.Onces for each of our languages for carriers for lang := range gen.CarrierData { carrierOnces[lang] = &sync.Once{} } for lang := range gen.GeocodingData { geocodingOnces[lang] = &sync.Once{} } } // GetTimezonesForPrefix returns a slice of Timezones corresponding to the number passed // or error when it is impossible to convert the string to int // The algorythm tries to match the timezones starting from the maximum // number of phone number digits and decreasing until it finds one or reaches 0 func GetTimezonesForPrefix(number string) ([]string, error) { var err error timezoneOnce.Do(func() { timezoneMap, err = loadIntStringArrayMap(gen.TimezoneData) }) if timezoneMap == nil { return nil, fmt.Errorf("error loading timezone map: %v", err) } // strip any leading + number = strings.TrimLeft(number, "+") matchLength := len(number) // maxLength: min( len(number), timezoneMap.MaxLength ) if matchLength > timezoneMap.MaxLength { matchLength = timezoneMap.MaxLength } for i := matchLength; i > 0; i-- { index, err := strconv.Atoi(number[0:i]) if err != nil { return nil, err } tzs, found := timezoneMap.Map[index] if found { return tzs, nil } } return []string{UNKNOWN_TIMEZONE}, nil } // GetTimezonesForNumber returns the names of timezones which we believe maps to the // passed in number. func GetTimezonesForNumber(number *PhoneNumber) ([]string, error) { e164 := Format(number, E164) return GetTimezonesForPrefix(e164) } func getValueForNumber(onceMap map[string]*sync.Once, langMap map[string]*intStringMap, binMap map[string]string, language string, maxLength int, number *PhoneNumber) (string, int, error) { // do we have data for this language _, existing := binMap[language] if !existing { return "", 0, nil } // load it into our map onceMap[language].Do(func() { prefixMap, err := loadPrefixMap(binMap[language]) if err == nil { langMap[language] = prefixMap } }) // do we have a map for this language? prefixMap, ok := langMap[language] if !ok { return "", 0, fmt.Errorf("error loading language map for %s", language) } e164 := Format(number, E164) l := len(e164) if maxLength > l { maxLength = l } for i := maxLength; i > 1; i-- { index, err := strconv.Atoi(e164[0:i]) if err != nil { return "", 0, err } if value, has := prefixMap.Map[index]; has { return value, index, nil } } return "", 0, nil } // GetCarrierForNumber returns the carrier we believe the number belongs to. Note due // to number porting this is only a guess, there is no guarantee to its accuracy. func GetCarrierForNumber(number *PhoneNumber, lang string) (string, error) { carrier, _, err := GetCarrierWithPrefixForNumber(number, lang) return carrier, err } // GetSafeCarrierDisplayNameForNumber Gets the name of the carrier for the given phone number // only when it is 'safe' to display to users. // A carrier name is considered safe if the number is valid and // for a region that doesn't support mobile number portability . func GetSafeCarrierDisplayNameForNumber(phoneNumber *PhoneNumber, lang string) (string, error) { if IsMobileNumberPortableRegion(GetRegionCodeForNumber(phoneNumber)) { return "", nil } return GetCarrierForNumber(phoneNumber, lang) } // GetCarrierWithPrefixForNumber returns the carrier we believe the number belongs to, as well as // its prefix. Note due to number porting this is only a guess, there is no guarantee to its accuracy. func GetCarrierWithPrefixForNumber(number *PhoneNumber, lang string) (string, int, error) { carrier, prefix, err := getValueForNumber(carrierOnces, carrierPrefixMap, gen.CarrierData, lang, 10, number) if err != nil { return "", 0, err } if carrier != "" { return carrier, prefix, nil } // fallback to english return getValueForNumber(carrierOnces, carrierPrefixMap, gen.CarrierData, "en", 10, number) } // GetGeocodingForNumber returns the location we think the number was first acquired in. This is // just our best guess, there is no guarantee to its accuracy. func GetGeocodingForNumber(number *PhoneNumber, lang string) (string, error) { geocoding, _, err := getValueForNumber(geocodingOnces, geocodingPrefixMap, gen.GeocodingData, lang, 10, number) if err != nil || geocoding != "" { return geocoding, err } // fallback to english geocoding, _, err = getValueForNumber(geocodingOnces, geocodingPrefixMap, gen.GeocodingData, "en", 10, number) if err != nil || geocoding != "" { return geocoding, err } // fallback to locale var reg language.Region if reg, err = language.ParseRegion(GetRegionCodeForNumber(number)); err != nil { return "", err } var langT language.Tag if langT, err = language.Parse(lang); err != nil { langT = language.English // fallback to english } return display.Regions(langT).Name(reg), nil }