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