module Phonelib # main module that includes all basic data and methods module Core # @private variable will include hash with data for validation @@phone_data = nil # eagerly initialize the gem, loads data into memory. not required, initialization is done lazily otherwise, but # may be desirable in production enviroments to avoid initialization time on first use. def eager_load! phone_data phone_ext_data end # getter for phone data for other modules of gem, can be used outside # @return [Hash] all data for phone parsing def phone_data @@phone_data ||= load_data.freeze end # @private getter for phone data indexed by country code (internal use only) def data_by_country_codes @@data_by_country_codes ||= phone_data.each_value.group_by { |d| d[COUNTRY_CODE] }.freeze end # @private getter for all international prefixes in phone_data def phone_data_int_prefixes @@all_int_prefixes ||= phone_data.map {|k,v| v[:international_prefix] }.select { |v| v != '' }.compact.uniq.join('|').freeze end # @private used to cache frequently-used regular expressions @@phone_regexp_cache = {} # @private getter for phone regexp cache (internal use only) def phone_regexp_cache @@phone_regexp_cache end # @private variable for storing geo/carrier/timezone data @@phone_ext_data = nil # @private getter for extended phone data def phone_ext_data @@phone_ext_data ||= load_ext_data.freeze end # @private default country for parsing variable setting @@default_country = nil # getter method for default_country variable # @return [String,Symbol,Array,nil] Default country ISO2 code or codes used for parsing def default_country @@default_country end # setter method for default_country variable # @param country [String,Symbol,Array] Default country ISO2 code or codes used for parsing # @return [String,Symbol,Array] Default country ISO2 code or codes used for parsing def default_country=(country) @@default_country = country end # @private extension separator @@extension_separator = ';' # getter method for extension_separator variable # @return [String] Default extension separator used for formatting def extension_separator @@extension_separator end # setter method for extension_separator variable # @param separator [String] extension separator used for formatting # @return [String] Default extension separator used for formatting def extension_separator=(separator) @@extension_separator = separator end # @private extension separator symbols for parsing @@extension_separate_symbols = '#;' # getter method for extension_separate_symbols variable # @return [String] Default extension separator symbols used for parsing def extension_separate_symbols @@extension_separate_symbols end # setter method for extension_separate_symbols variable # @param separator [String] extension separator symbols used for parsing # @return [String] Default extension separator symbols used for parsing def extension_separate_symbols=(separator) @@extension_separate_symbols = separator end # @private flag identifies whether to use special phone types, \ # like short code @@parse_special = false # getter for flag for special phone types parsing # @return [Boolean] Flag defines whether to parse special phone types def parse_special @@parse_special end # setter for flag for special phone types parsing # @param special [Boolean] parse special phone types value # @return [Boolean] Flag defines whether to parse special phone types def parse_special=(special) @@parse_special = special end # @private strict check for validator, doesn't sanitize number @@strict_check = false # getter for strict check flag # @return [Boolean] Flag defines whether to do strict parsing check def strict_check @@strict_check end # setter for strict check flag # @param strict [Boolean] make a strict parsing or not # @return [Boolean] Flag defines whether to do strict parsing check def strict_check=(strict) @@strict_check = strict end # @private don't use plus sign for automatic country change @@ignore_plus = false # getter for ignore plus flag # @return [Boolean] Flag defines whether to reset country in case number has + and country prefix doesn't match def ignore_plus @@ignore_plus end # setter for ignore plus flag # @param ignore_plus [Boolean] ignore plus sign or not # @return [Boolean] Flag defines whether to ignore plus for country reset during validations in case country prefix doesn't match def ignore_plus=(ignore_plus) @@ignore_plus = ignore_plus end # @private sanitizing regex, matching symbols will get removed from parsed number, must be string @@sanitize_regex = '[^0-9]+' # getter for sanitize regex # @return [String] regex of symbols to wipe from parsed number def sanitize_regex @@sanitize_regex end # setter for sanitize regex # @param regex [String] symbols to wipe from parsed number # @return [String] regex of symbols to wipe from parsed number def sanitize_regex=(regex) @@sanitize_regex = regex.is_a?(String) ? regex : regex.to_s end # @private strict double prefix check for validator, doesn't sanitize number @@strict_double_prefix_check = false # getter for strict double prefix check flag # @return [Boolean] Flag defines whether to do strict double prefix parsing check def strict_double_prefix_check @@strict_double_prefix_check end # setter for strict double prefix check flag # @param strict [Boolean] make a strict double prefix parsing or not # @return [Boolean] Flag defines whether to do strict double prefix parsing check def strict_double_prefix_check=(strict) @@strict_double_prefix_check = strict end @@override_phone_data = nil # setter for data file to use def override_phone_data=(file_path) @@override_phone_data = file_path end def override_phone_data @@override_phone_data end @@additional_regexes = {} # setter for data file to use def additional_regexes=(data) return unless data.is_a?(Array) @@additional_regexes = {} data.each do |row| next if row.size != 3 add_additional_regex(*row) end end def add_additional_regex(country, type, national_regex) return unless Phonelib::Core::TYPES_DESC.keys.include?(type.to_sym) return unless national_regex.is_a?(String) @@phone_data = @@data_by_country_codes = nil @@additional_regexes[country.to_s.upcase] ||= {} @@additional_regexes[country.to_s.upcase][type] ||= [] @@additional_regexes[country.to_s.upcase][type] << national_regex end def dump_additional_regexes rows = [] @@additional_regexes.each do |country, types| types.each do |type, regexes| regexes.each do |regex| rows << [country, type, regex] end end end rows end def additional_regexes @@additional_regexes end @@vanity_conversion = false # setter for vanity phone numbers chars replacement def vanity_conversion=(value) @@vanity_conversion = value end def vanity_conversion @@vanity_conversion end # gem constants definition # @private Main data file FILE_MAIN_DATA = 'data/phone_data.dat' # @private Extended data file FILE_EXT_DATA = 'data/extended_data.dat' # constants for phone types # Validation patterns keys constants # @private General pattern for country key GENERAL = :general_desc # @private Freephone line pattern key PREMIUM_RATE = :premium_rate # @private Freephone line pattern key TOLL_FREE = :toll_free # @private Shared cost pattern key. The cost of this call is shared # between caller and recipient, and is hence typically less than # PREMIUM_RATE calls SHARED_COST = :shared_cost # @private VoIP pattern key. This includes TSoIP (Telephony Service over IP) VOIP = :voip # @private A personal number is associated with a particular person, # and may be routed to either a MOBILE or FIXED_LINE number. PERSONAL_NUMBER = :personal_number # @private Pager phone number pattern key PAGER = :pager # @private 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. UAN = :uan # @private Used for 'Voice Mail Access Numbers'. VOICEMAIL = :voicemail # @private Fixed line pattern key FIXED_LINE = :fixed_line # @private Mobile phone number pattern key MOBILE = :mobile # @private In case MOBILE and FIXED patterns are the same, # this type is returned FIXED_OR_MOBILE = :fixed_or_mobile # @private Short code SHORT_CODE = :short_code # @private emergency numbers EMERGENCY = :emergency # @private carrier specific type CARRIER_SPECIFIC = :carrier_specific # @private SMS Services only type SMS_SERVICES = :sms_services # @private expendad emergency type EXPANDED_EMERGENCY = :expanded_emergency # @private no international dialling type NO_INTERNATIONAL_DIALING = :no_international_dialling # @private carrier services type CARRIER_SERVICES = :carrier_services # @private directory services DIRECTORY_SERVICES = :directory_services # @private standard rate type STANDARD_RATE = :standard_rate # @private carrier selection codes CARRIER_SELECTION_CODES = :carrier_selection_codes # @private area code optional type AREA_CODE_OPTIONAL = :area_code_optional # Internal use keys for validations # @private Valid regex pattern key VALID_PATTERN = :national_number_pattern # @private Possible regex pattern key POSSIBLE_PATTERN = :possible_number_pattern # @private National prefix key NATIONAL_PREFIX = :national_prefix # @private National prefix for parsing key NATIONAL_PREFIX_FOR_PARSING = :national_prefix_for_parsing # @private National prefix transform rule key NATIONAL_PREFIX_TRANSFORM_RULE = :national_prefix_transform_rule # @private National prefix rule key NATIONAL_PREFIX_RULE = :national_prefix_formatting_rule # @private Country code key COUNTRY_CODE = :country_code # @private Leading digits key LEADING_DIGITS = :leading_digits # @private International prefix key INTERNATIONAL_PREFIX = :international_prefix # @private Main country for code key MAIN_COUNTRY_FOR_CODE = :main_country_for_code # @private Double country prefix flag key DOUBLE_COUNTRY_PREFIX_FLAG = :double_prefix # @private Types key TYPES = :types # @private Formats key FORMATS = :formats # @private Pattern key PATTERN = :pattern # @private Short key SHORT = :short # @private Plus sign PLUS_SIGN = '+'.freeze # @private vanity numbers 4 keys letters VANITY_4_LETTERS_KEYS_REGEX = /[SVYZ]/.freeze # @private Area code possible types AREA_CODE_TYPES = [FIXED_LINE, FIXED_OR_MOBILE, MOBILE].freeze # @private Area code countries for mobile type AREA_CODE_MOBILE_COUNTRIES = %w(AR MX BR).freeze # @private Area code mobile phone token AREA_CODE_MOBILE_TOKENS = { 'MX' => '1', 'AR' => '9' }.freeze # @private Default number formatting data hash DEFAULT_NUMBER_FORMAT = { pattern: '(\\d+)(\\d{3})(\\d{4})', format: '$1 $2 $3' }.freeze # @private hash of all phone types with human representation TYPES_DESC = { general_desc: 'General Pattern', premium_rate: 'Premium Rate', toll_free: 'Toll Free', shared_cost: 'Shared Cost', voip: 'VoIP', personal_number: 'Personal Number', pager: 'Pager', uan: 'UAN', voicemail: 'VoiceMail', fixed_line: 'Fixed Line', mobile: 'Mobile', fixed_or_mobile: 'Fixed Line or Mobile', short_code: 'Short code', emergency: 'Emergency services', carrier_specific: 'Carrier specific number', sms_services: 'SMS Services only phone', expanded_emergency: 'Expanded emergency', no_international_dialling: 'No International Dialing phone', carrier_services: 'Carrier Services', directory_services: 'Directory Services', standard_rate: 'Standard Rate Destination', carrier_selection_codes: 'Carrier Selection codes', area_code_optional: 'Are code optional' }.freeze # @private short codes types keys SHORT_CODES = [ :short_code, :emergency, :carrier_specific, :sms_services, :expanded_emergency, :no_international_dialling, :carrier_services, :directory_services, :standard_rate, :carrier_selection_codes, :area_code_optional ].freeze # @private Extended data prefixes hash key EXT_PREFIXES = :prefixes # @private Extended data geo names array key EXT_GEO_NAMES = :geo_names # @private Extended data country names array key EXT_COUNTRY_NAMES = :country_names # @private Extended data timezones array key EXT_TIMEZONES = :timezones # @private Extended data carriers array key EXT_CARRIERS = :carriers # @private Extended data key for geoname in prefixes hash EXT_GEO_NAME_KEY = :g # @private Extended data key for timezone in prefixes hash EXT_TIMEZONE_KEY = :t # @private Extended data key for carrier in prefixes hash EXT_CARRIER_KEY = :c # method for parsing phone number. # On first run fills @@phone_data with data present in yaml file # @param phone [String] the phone number to be parsed # @param passed_country [nil|String|Symbol] country for phone parsing # @return [Phonelib::Phone] parsed phone entity def parse(phone, passed_country = nil) Phonelib::Phone.new phone, passed_country end # method checks if passed phone number is valid # @param phone_number [String] the phone number to be parsed # @return [Boolean] phone valid or not def valid?(phone_number) parse(phone_number).valid? end # method checks if passed phone number is invalid # @param phone_number [String] the phone number to be parsed # @return [Boolean] phone invalid or not def invalid?(phone_number) parse(phone_number).invalid? end # method checks if passed phone number is possible # @param phone_number [String] the phone number to be parsed # @return [Boolean] phone possible or not def possible?(phone_number) parse(phone_number).possible? end # method checks if passed phone number is impossible # @param phone_number [String] the phone number to be parsed # @return [Boolean] phone impossible or not def impossible?(phone_number) parse(phone_number).impossible? end # method checks if passed phone number is valid for provided country # @param phone_number [String] the phone number to be parsed # @param country [String] ISO2 country code for phone parsing # @return [Boolean] phone valid for specified country or not def valid_for_country?(phone_number, country) parse(phone_number, country).valid_for_country?(country) end # method checks if passed phone number is invalid for provided country # @param phone_number [String] the phone number to be parsed # @param country [String] ISO2 country code for phone parsing # @return [Boolean] phone invalid for specified country or not def invalid_for_country?(phone_number, country) parse(phone_number, country).invalid_for_country?(country) end private # @private Load data file into memory def load_data data_file = "#{File.dirname(__FILE__)}/../../#{FILE_MAIN_DATA}" default_data = Marshal.load(File.binread(data_file)) if override_phone_data override_data_file = Marshal.load(File.binread(override_phone_data)) default_data.merge!(override_data_file) end additional_regexes.each do |country, types| types.each do |type, regex| default_data[country][Core::TYPES][type] ||= {} [Core::VALID_PATTERN, Core::POSSIBLE_PATTERN].each do |key| if default_data[country][Core::TYPES][type][key] default_data[country][Core::TYPES][type][key] << "|#{regex.join('|')}" else default_data[country][Core::TYPES][type][key] = regex.join('|') end if type != Core::GENERAL default_data[country][Core::TYPES][Core::GENERAL][key] << "|#{regex.join('|')}" end end end end default_data end # @private Load extended data file into memory def load_ext_data data_file = "#{File.dirname(__FILE__)}/../../#{FILE_EXT_DATA}" Marshal.load(File.binread(data_file)) end end end