lib/phonelib/phone_analyzer.rb in phonelib-0.4.6 vs lib/phonelib/phone_analyzer.rb in phonelib-0.4.7
- old
+ new
@@ -2,10 +2,15 @@
# phone analyzing methods module
module PhoneAnalyzer
# array of types not included for validation check in cycle
NOT_FOR_CHECK = [:general_desc, :fixed_line, :mobile, :fixed_or_mobile]
+ # caches regular expression, reusing it for later lookups
+ def cr(regexp)
+ Phonelib.phone_regexp_cache[regexp] ||= Regexp.new(regexp)
+ end
+
# parses provided phone if it is valid for country data and returns result of
# analyze
#
# ==== Attributes
#
@@ -32,33 +37,39 @@
# ==== Attributes
#
# * +phone+ - phone for parsing
# * +country+ - country to parse phone with
def try_to_parse_single_country(phone, country)
- if country && Phonelib.phone_data[country]
+ data = Phonelib.phone_data[country]
+ if country && data
# if country was provided and it's a valid country, trying to
# create e164 representation of phone number,
# kind of normalization for parsing
- e164 = convert_to_e164 phone, Phonelib.phone_data[country]
+ e164 = convert_to_e164 phone, data
# if phone starts with international prefix of provided
# country try to reanalyze without international prefix for
# all countries
return analyze(e164.gsub('+', ''), nil) if e164[0] == '+'
# trying to parse number for provided country
- parse_single_country e164, Phonelib.phone_data[country]
+ parse_single_country e164, data
end
end
# method checks if phone is valid against single provided country data
#
# ==== Attributes
#
# * +e164+ - e164 representation of phone for parsing
# * +data+ - country data for single country for parsing
def parse_single_country(e164, data)
- country_match = phone_match_data?(e164, data)
- country_match && get_national_and_data(e164, data, country_match)
+ valid_match = phone_match_data?(e164, data)
+ if valid_match
+ get_national_and_data(e164, data, valid_match)
+ else
+ possible_match = phone_match_data?(e164, data, true)
+ possible_match && get_national_and_data(e164, data, possible_match)
+ end
end
# method tries to detect what is the country for provided phone
#
# ==== Attributes
@@ -88,38 +99,41 @@
# ==== Attributes
#
# * +phone+ - phone number for parsing
# * +data+ - country data to be based on for creating e164 representation
def convert_to_e164(phone, data)
- match = phone.match full_valid_regex_for_data(data)
- if match
+ match = phone.match full_regex_for_data(data, Core::VALID_PATTERN)
+ case
+ when match
national_start = (1..3).map { |i| match[i].to_s.length }.inject(:+)
"#{data[Core::COUNTRY_CODE]}#{phone[national_start..-1]}"
+ when phone.match(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"))
+ phone.sub(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"), '+')
else
- phone.sub(/^#{data[Core::INTERNATIONAL_PREFIX]}/, '+')
+ "#{data[Core::COUNTRY_CODE]}#{phone}"
end
end
# constructs full regex for phone validation for provided phone data
# (international prefix, country code, national prefix, valid number)
#
# ==== Attributes
#
# * +data+ - country data hash
# * +country_optional+ - whether to put country code as optional group
- def full_valid_regex_for_data(data, country_optional = true)
+ def full_regex_for_data(data, type, country_optional = true)
regex = []
regex << "(#{data[Core::INTERNATIONAL_PREFIX]})?"
regex << if country_optional
"(#{data[Core::COUNTRY_CODE]})?"
else
data[Core::COUNTRY_CODE]
end
- regex << "(#{data[Core::NATIONAL_PREFIX]})?"
- regex << "(#{data[Core::TYPES][Core::GENERAL][Core::VALID_PATTERN]})"
+ regex << "(#{data[Core::NATIONAL_PREFIX_FOR_PARSING] || data[Core::NATIONAL_PREFIX]})?"
+ regex << "(#{data[Core::TYPES][Core::GENERAL][type]})"
- /^#{regex.join}$/
+ cr("^#{regex.join}$")
end
# returns national number and analyzing results for provided phone number
#
# ==== Attributes
@@ -128,11 +142,11 @@
# * +data+ - country data
# * +country_match+ - result of match of phone within full regex
def get_national_and_data(phone, data, country_match)
prefix_length = data[Core::COUNTRY_CODE].length
prefix_length += [1, 2].map { |i| country_match[i].to_s.size }.inject(:+)
- result = data.select { |k, v| ![:types, :formats].include?(k) }
+ result = data.select { |k, v| k != :types && k != :formats }
result[:national] = phone[prefix_length..-1]
result[:format] = get_number_format(result[:national],
data[Core::FORMATS])
result.merge! all_number_types(result[:national], data[Core::TYPES])
{ result[:id] => result }
@@ -142,16 +156,17 @@
#
# ==== Attributes
#
# * +phone+ - phone number for parsing
# * +data+ - country data
- def phone_match_data?(phone, data)
+ def phone_match_data?(phone, data, possible = false)
country_code = "#{data[Core::COUNTRY_CODE]}"
inter_prefix = "(#{data[Core::INTERNATIONAL_PREFIX]})?"
- if phone =~ /^#{inter_prefix}#{country_code}/
- phone.match full_valid_regex_for_data(data, false)
- end
+ return unless phone.match cr("^#{inter_prefix}#{country_code}")
+
+ type = possible ? Core::POSSIBLE_PATTERN : Core::VALID_PATTERN
+ phone.match full_regex_for_data(data, type, false)
end
# Returns all valid and possible phone number types for currently parsed
# phone for provided data hash.
#
@@ -191,12 +206,12 @@
# * +national+ - national phone number
# * +format_data+ - formatting data from country data
def get_number_format(national, format_data)
format_data && format_data.find do |format|
(format[Core::LEADING_DIGITS].nil? \
- || /^(#{format[Core::LEADING_DIGITS]})/ =~ national) \
- && /^(#{format[Core::PATTERN]})$/ =~ national
+ || national.match(cr("^(#{format[Core::LEADING_DIGITS]})"))) \
+ && national.match(cr("^(#{format[Core::PATTERN]})$"))
end || Core::DEFAULT_NUMBER_FORMAT
end
# Checks if fixed line pattern and mobile pattern are the same and returns
# appropriate keys
@@ -217,39 +232,36 @@
# ==== Attributes
#
# * +all_patterns+ - hash of all patterns for validation
# * +type+ - type of phone to get patterns for
def get_patterns(all_patterns, type)
- patterns = case type
- when Core::FIXED_OR_MOBILE
- all_patterns[Core::FIXED_LINE]
- else
- all_patterns[type]
- end
- return [nil, nil] if patterns.nil?
- national_pattern = patterns[Core::VALID_PATTERN]
- possible_pattern = patterns[Core::POSSIBLE_PATTERN] || national_pattern
+ type = Core::FIXED_LINE if type == Core::FIXED_OR_MOBILE
+ patterns = all_patterns[type]
- [possible_pattern, national_pattern]
+ if patterns.nil?
+ [nil, nil]
+ else
+ [patterns[Core::POSSIBLE_PATTERN], patterns[Core::VALID_PATTERN]]
+ end
end
# Checks if passed number matches valid and possible patterns
#
# ==== Attributes
#
# * +number+ - phone number for validation
# * +possible_pattern+ - possible pattern for validation
# * +national_pattern+ - valid pattern for validation
def number_valid_and_possible?(number, possible_pattern, national_pattern)
- possible_match = number.match(/^(?:#{possible_pattern})$/)
+ possible_match = number.match(cr("^(?:#{possible_pattern})$"))
possible = possible_match && possible_match.to_s.length == number.length
+ return [possible, possible] if possible_pattern == national_pattern
+ valid = false
if possible
# doing national pattern match only in case possible matches
- national_match = number.match(/^(?:#{national_pattern})$/)
+ national_match = number.match(cr("^(?:#{national_pattern})$"))
valid = national_match && national_match.to_s.length == number.length
- else
- valid = false
end
[valid && possible, possible]
end
end