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
}