matcher.go (153 lines of code) (raw):

package phonenumbers import ( "regexp" "strconv" "strings" "unicode" ) type PhoneNumberMatcher struct { } func NewPhoneNumberMatcher(seq string) *PhoneNumberMatcher { // TODO(ttacon): to be implemented return nil } func ContainsOnlyValidXChars(number *PhoneNumber, candidate string) bool { // The characters 'x' and 'X' can be (1) a carrier code, in which // case they always precede the national significant number or (2) // an extension sign, in which case they always precede the extension // number. We assume a carrier code is more than 1 digit, so the first // case has to have more than 1 consecutive 'x' or 'X', whereas the // second case can only have exactly 1 'x' or 'X'. We ignore the // character if it appears as the last character of the string. for index := 0; index < len(candidate)-1; index++ { var charAtIndex = candidate[index] if charAtIndex == 'x' || charAtIndex == 'X' { var charAtNextIndex = candidate[index+1] if charAtNextIndex == 'x' || charAtNextIndex == 'X' { // This is the carrier code case, in which the 'X's // always precede the national significant number. index++ if IsNumberMatchWithOneNumber(number, candidate[index:]) != NSN_MATCH { return false } // This is the extension sign case, in which the 'x' // or 'X' should always precede the extension number. } else if NormalizeDigitsOnly(candidate[index:]) != number.GetExtension() { return false } } } return true } func IsNationalPrefixPresentIfRequired(number *PhoneNumber) bool { // First, check how we deduced the country code. If it was written // in international format, then the national prefix is not required. if number.GetCountryCodeSource() != PhoneNumber_FROM_DEFAULT_COUNTRY { return true } var phoneNumberRegion = GetRegionCodeForCountryCode(int(number.GetCountryCode())) var metadata = getMetadataForRegion(phoneNumberRegion) if metadata == nil { return true } // Check if a national prefix should be present when formatting this number. var nationalNumber = GetNationalSignificantNumber(number) var formatRule = chooseFormattingPatternForNumber( metadata.GetNumberFormat(), nationalNumber) // To do this, we check that a national prefix formatting rule was // present and that it wasn't just the first-group symbol ($1) with // punctuation. if (formatRule != nil) && len(formatRule.GetNationalPrefixFormattingRule()) > 0 { if formatRule.GetNationalPrefixOptionalWhenFormatting() { // The national-prefix is optional in these cases, so we // don't need to check if it was present. return true } if formattingRuleHasFirstGroupOnly( formatRule.GetNationalPrefixFormattingRule()) { // National Prefix not needed for this number. return true } // Normalize the remainder. var rawInputCopy = NormalizeDigitsOnly(number.GetRawInput()) var rawInput = NewBuilderString(rawInputCopy) // Check if we found a national prefix and/or carrier code at // the start of the raw input, and return the result. return maybeStripNationalPrefixAndCarrierCode( rawInput, metadata, NewBuilder(nil)) } return true } func ContainsMoreThanOneSlashInNationalNumber( number *PhoneNumber, candidate string) bool { var firstSlash = strings.Index(candidate, "/") if firstSlash < 0 { // No slashes, this is okay. return false } // Now look for a second one. var secondSlash = strings.Index(candidate[firstSlash+1:], "/") if secondSlash < 0 { // Only one slash, this is okay. return false } // If the first slash is after the country calling code, this is permitted. var candidateHasCountryCode = (number.GetCountryCodeSource() == PhoneNumber_FROM_NUMBER_WITH_PLUS_SIGN || number.GetCountryCodeSource() == PhoneNumber_FROM_NUMBER_WITHOUT_PLUS_SIGN) cc := strconv.Itoa(int(number.GetCountryCode())) if candidateHasCountryCode && NormalizeDigitsOnly(candidate[0:firstSlash]) == cc { // Any more slashes and this is illegal. return strings.Contains(candidate[secondSlash+1:], "/") } return true } func CheckNumberGroupingIsValid( number *PhoneNumber, candidate string, fn func(*PhoneNumber, string, []string) bool) bool { // TODO(ttacon): to be implemented return false } func AllNumberGroupsRemainGrouped( number *PhoneNumber, normalizedCandidate string, formattedNumberGroups []string) bool { var fromIndex = 0 if number.GetCountryCodeSource() != PhoneNumber_FROM_DEFAULT_COUNTRY { // First skip the country code if the normalized candidate contained it. var cc = strconv.Itoa(int(number.GetCountryCode())) fromIndex = strings.Index(normalizedCandidate, cc) + len(cc) } // Check each group of consecutive digits are not broken into // separate groupings in the normalizedCandidate string. for i := 0; i < len(formattedNumberGroups); i++ { // Fails if the substring of normalizedCandidate starting // from fromIndex doesn't contain the consecutive digits // in formattedNumberGroups[i]. fromIndex = strings.Index( normalizedCandidate[fromIndex+1:], formattedNumberGroups[i]) if fromIndex < 0 { return false } // Moves fromIndex forward. fromIndex += len(formattedNumberGroups[i]) if i == 0 && fromIndex < len(normalizedCandidate) { // We are at the position right after the NDC. We get // the region used for formatting information based on // the country code in the phone number, rather than the // number itself, as we do not need to distinguish between // different countries with the same country calling code // and this is faster. var region = GetRegionCodeForCountryCode(int(number.GetCountryCode())) if GetNddPrefixForRegion(region, true) != "" && unicode.IsDigit(rune(normalizedCandidate[fromIndex])) { // This means there is no formatting symbol after the // NDC. In this case, we only accept the number if there // is no formatting symbol at all in the number, except // for extensions. This is only important for countries // with national prefixes. var nationalSignificantNumber = GetNationalSignificantNumber(number) return strings.HasPrefix( normalizedCandidate[fromIndex-len(formattedNumberGroups[i]):], nationalSignificantNumber) } } } // The check here makes sure that we haven't mistakenly already // used the extension to match the last group of the subscriber // number. Note the extension cannot have formatting in-between digits. return strings.Contains(normalizedCandidate[fromIndex:], number.GetExtension()) } func AllNumberGroupsAreExactlyPresent( number *PhoneNumber, normalizedCandidate string, formattedNumberGroups []string) bool { var candidateGroups = NON_DIGITS_PATTERN.FindAllString(normalizedCandidate, -1) // Set this to the last group, skipping it if the number has an extension. var candidateNumberGroupIndex = len(candidateGroups) - 2 if number.GetExtension() != "" { candidateNumberGroupIndex = len(candidateGroups) - 1 } // First we check if the national significant number is formatted // as a block. We use contains and not equals, since the national // significant number may be present with a prefix such as a national // number prefix, or the country code itself. if len(candidateGroups) == 1 || strings.Contains( candidateGroups[candidateNumberGroupIndex], GetNationalSignificantNumber(number)) { return true } // Starting from the end, go through in reverse, excluding the first // group, and check the candidate and number groups are the same. for formattedNumberGroupIndex := len(formattedNumberGroups) - 1; formattedNumberGroupIndex > 0 && candidateNumberGroupIndex >= 0; formattedNumberGroupIndex-- { if candidateGroups[candidateNumberGroupIndex] != formattedNumberGroups[formattedNumberGroupIndex] { return false } candidateNumberGroupIndex-- } // Now check the first group. There may be a national prefix at // the start, so we only check that the candidate group ends with // the formatted number group. return (candidateNumberGroupIndex >= 0 && strings.HasSuffix(candidateGroups[candidateNumberGroupIndex], formattedNumberGroups[0])) } // Returns whether the given national number (a string containing only decimal digits) matches // the national number pattern defined in the given PhoneNumberDesc message. func MatchNationalNumber(number string, numberDesc PhoneNumberDesc, allowPrefixMatch bool) bool { nationalNumberPattern := numberDesc.GetNationalNumberPattern() // We don't want to consider it a prefix match when matching non-empty input against an empty pattern. if len(nationalNumberPattern) == 0 { return false } regex := regexFor(nationalNumberPattern) return match(number, regex, allowPrefixMatch) } func match(number string, pattern *regexp.Regexp, allowPrefixMatch bool) bool { ind := pattern.FindStringIndex(number) if len(ind) == 0 || ind[0] != 0 { return false } patP := `^(?:` + pattern.String() + `)$` // Strictly match pat := regexFor(patP) return pat.MatchString(number) || allowPrefixMatch }