phonelib 0.6.10 → 0.6.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/extended_data.dat +0 -0
- data/data/phone_data.dat +0 -0
- data/lib/phonelib/core.rb +11 -6
- data/lib/phonelib/data_importer.rb +22 -56
- data/lib/phonelib/data_importer_helper.rb +29 -3
- data/lib/phonelib/phone.rb +5 -5
- data/lib/phonelib/phone_analyzer.rb +28 -35
- data/lib/phonelib/phone_analyzer_helper.rb +38 -31
- data/lib/phonelib/phone_formatter.rb +15 -15
- data/lib/phonelib/version.rb +1 -1
- data/lib/validators/phone_validator.rb +16 -5
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa38e795e6d299330a008f39f359cdd29ba221fc
|
4
|
+
data.tar.gz: 9bf8ce164c4838722a06aff72037147852100244
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 61367e9387768b79bbb6d43c5b9718098c4a6772281b24111acd9bce2e6dadcfe08b9aa54aa64e4463b4457e593d4fb08e92da6e15d537dcf083df14d3c5e3f1
|
7
|
+
data.tar.gz: 58ba6833ce8aec90171faacef75b9fbfd865cd236242cb621c4fff26e457b4dc387c49d9e2293c7b9e0d77eac36d8442cbba830f1fe68011fff4539f54374758
|
data/data/extended_data.dat
CHANGED
Binary file
|
data/data/phone_data.dat
CHANGED
Binary file
|
data/lib/phonelib/core.rb
CHANGED
@@ -218,23 +218,28 @@ module Phonelib
|
|
218
218
|
# @private Pattern key
|
219
219
|
PATTERN = :pattern
|
220
220
|
|
221
|
+
PLUS_SIGN = '+'.freeze
|
222
|
+
|
223
|
+
# @private vanity numbers 4 keys letters
|
224
|
+
VANITY_4_LETTERS_KEYS_REGEX = /[SVYZ]/.freeze
|
225
|
+
|
221
226
|
# @private Area code possible types
|
222
|
-
AREA_CODE_TYPES = [FIXED_LINE, FIXED_OR_MOBILE, MOBILE]
|
227
|
+
AREA_CODE_TYPES = [FIXED_LINE, FIXED_OR_MOBILE, MOBILE].freeze
|
223
228
|
|
224
229
|
# @private Area code countries for mobile type
|
225
|
-
AREA_CODE_MOBILE_COUNTRIES = %w(AR MX BR)
|
230
|
+
AREA_CODE_MOBILE_COUNTRIES = %w(AR MX BR).freeze
|
226
231
|
|
227
232
|
# @private Area code mobile phone token
|
228
233
|
AREA_CODE_MOBILE_TOKENS = {
|
229
234
|
'MX' => '1',
|
230
235
|
'AR' => '9'
|
231
|
-
}
|
236
|
+
}.freeze
|
232
237
|
|
233
238
|
# @private Default number formatting data hash
|
234
239
|
DEFAULT_NUMBER_FORMAT = {
|
235
240
|
pattern: '(\\d+)(\\d{3})(\\d{4})',
|
236
241
|
format: '$1 $2 $3'
|
237
|
-
}
|
242
|
+
}.freeze
|
238
243
|
|
239
244
|
# @private hash of all phone types with human representation
|
240
245
|
TYPES_DESC = {
|
@@ -261,7 +266,7 @@ module Phonelib
|
|
261
266
|
standard_rate: 'Standard Rate Destination',
|
262
267
|
carrier_selection_codes: 'Carrier Selection codes',
|
263
268
|
area_code_optional: 'Are code optional'
|
264
|
-
}
|
269
|
+
}.freeze
|
265
270
|
|
266
271
|
# @private short codes types keys
|
267
272
|
SHORT_CODES = [
|
@@ -269,7 +274,7 @@ module Phonelib
|
|
269
274
|
:expanded_emergency, :no_international_dialling, :carrier_services,
|
270
275
|
:directory_services, :standard_rate, :carrier_selection_codes,
|
271
276
|
:area_code_optional
|
272
|
-
]
|
277
|
+
].freeze
|
273
278
|
|
274
279
|
# @private Extended data prefixes hash key
|
275
280
|
EXT_PREFIXES = :prefixes
|
@@ -58,29 +58,7 @@ module Phonelib
|
|
58
58
|
import_timezone_data
|
59
59
|
import_carrier_data
|
60
60
|
save_data_file
|
61
|
-
|
62
|
-
|
63
|
-
# method saves parsed data to data files
|
64
|
-
def save_data_file
|
65
|
-
data_file =
|
66
|
-
"#{File.dirname(__FILE__)}/../../#{Phonelib::Core::FILE_MAIN_DATA}"
|
67
|
-
|
68
|
-
File.open(data_file, 'wb+') do |f|
|
69
|
-
Marshal.dump(@data, f)
|
70
|
-
end
|
71
|
-
|
72
|
-
ext_file =
|
73
|
-
"#{File.dirname(__FILE__)}/../../#{Phonelib::Core::FILE_EXT_DATA}"
|
74
|
-
extended = {
|
75
|
-
Phonelib::Core::EXT_PREFIXES => @prefixes,
|
76
|
-
Phonelib::Core::EXT_GEO_NAMES => @geo_names,
|
77
|
-
Phonelib::Core::EXT_TIMEZONES => @timezones,
|
78
|
-
Phonelib::Core::EXT_CARRIERS => @carriers
|
79
|
-
}
|
80
|
-
File.open(ext_file, 'wb+') do |f|
|
81
|
-
Marshal.dump(extended, f)
|
82
|
-
end
|
83
|
-
puts 'DATA SAVED'
|
61
|
+
save_extended_data_file
|
84
62
|
end
|
85
63
|
|
86
64
|
# method clones libphonenumber repo to local dir
|
@@ -97,10 +75,8 @@ module Phonelib
|
|
97
75
|
puts 'IMPORTING MAIN DATA'
|
98
76
|
main_from_xml("#{@destination}#{MAIN_FILE}").elements.each do |el|
|
99
77
|
# each country
|
100
|
-
country =
|
101
|
-
country
|
102
|
-
|
103
|
-
country.merge! get_types_and_formats(el.children)
|
78
|
+
country = hash_from_xml(el, :attributes)
|
79
|
+
country.merge! types_and_formats(el.children)
|
104
80
|
country = add_double_country_flag country
|
105
81
|
@data[country[:id]] = country
|
106
82
|
end
|
@@ -111,17 +87,11 @@ module Phonelib
|
|
111
87
|
puts 'IMPORTING SHORT NUMBER DATA'
|
112
88
|
main_from_xml("#{@destination}#{SHORT_DATA_FILE}").elements.each do |el|
|
113
89
|
# each country
|
114
|
-
country =
|
115
|
-
country
|
116
|
-
|
117
|
-
country.merge! get_types_and_formats(el.children)
|
90
|
+
country = hash_from_xml(el, :attributes)
|
91
|
+
country.merge! types_and_formats(el.children)
|
118
92
|
|
119
93
|
country[:types].each do |type, data|
|
120
|
-
|
121
|
-
merge_short_with_main_type(country[:id], type, data)
|
122
|
-
else
|
123
|
-
@data[country[:id]][:types][type] = data
|
124
|
-
end
|
94
|
+
merge_short_with_main_type(country[:id], type, data)
|
125
95
|
end
|
126
96
|
end
|
127
97
|
end
|
@@ -134,10 +104,8 @@ module Phonelib
|
|
134
104
|
el.children.each do |phone_type|
|
135
105
|
next unless phone_type.name == 'availableFormats'
|
136
106
|
|
137
|
-
|
138
|
-
|
139
|
-
country_code = el.attribute('countryCode').value
|
140
|
-
@data[get_country_by_code(country_code)][:formats] += formats
|
107
|
+
country_code = country_by_code(el.attribute('countryCode').value)
|
108
|
+
@data[country_code][:formats] += parse_formats(phone_type.children)
|
141
109
|
end
|
142
110
|
end
|
143
111
|
end
|
@@ -175,26 +143,24 @@ module Phonelib
|
|
175
143
|
end
|
176
144
|
|
177
145
|
# method extracts formats and types from xml data
|
178
|
-
def
|
179
|
-
|
180
|
-
formats = []
|
146
|
+
def types_and_formats(children)
|
147
|
+
result = { types: {}, formats: [] }
|
181
148
|
|
182
149
|
without_comments(children).each do |phone_type|
|
183
150
|
if phone_type.name == 'availableFormats'
|
184
|
-
formats = parse_formats(phone_type.children)
|
151
|
+
result[:formats] = parse_formats(phone_type.children)
|
185
152
|
else
|
186
|
-
types[name2sym(phone_type.name)] =
|
187
|
-
|
153
|
+
result[:types][name2sym(phone_type.name)] =
|
154
|
+
hash_from_xml(phone_type, :element)
|
188
155
|
end
|
189
156
|
end
|
190
157
|
|
191
|
-
|
192
|
-
|
193
|
-
{ types: types, formats: formats }
|
158
|
+
fill_possible_to_types_if_nil(result)
|
194
159
|
end
|
195
160
|
|
196
161
|
# method adds short number patterns to main data parsed from main xml
|
197
162
|
def merge_short_with_main_type(country_id, type, data)
|
163
|
+
@data[country_id][:types][type] ||= {}
|
198
164
|
data.each do |k, v|
|
199
165
|
if @data[country_id][:types][type][k]
|
200
166
|
@data[country_id][:types][type][k] += "|#{v}"
|
@@ -205,20 +171,20 @@ module Phonelib
|
|
205
171
|
end
|
206
172
|
|
207
173
|
# adds possible pattern in case it doesn't exists
|
208
|
-
def
|
209
|
-
types.each do |type, data|
|
174
|
+
def fill_possible_to_types_if_nil(result)
|
175
|
+
result[:types].each do |type, data|
|
210
176
|
if data[Core::VALID_PATTERN] && !data[Core::POSSIBLE_PATTERN]
|
211
|
-
types[type][Core::POSSIBLE_PATTERN] =
|
177
|
+
result[:types][type][Core::POSSIBLE_PATTERN] =
|
212
178
|
data[Core::VALID_PATTERN]
|
213
179
|
end
|
214
180
|
end
|
215
|
-
|
181
|
+
result
|
216
182
|
end
|
217
183
|
|
218
184
|
# method parses xml for formats data
|
219
185
|
def parse_formats(formats_children)
|
220
186
|
without_comments(formats_children).map do |format|
|
221
|
-
current_format =
|
187
|
+
current_format = hash_from_xml(format, :children)
|
222
188
|
|
223
189
|
without_comments(format.children).each do |f|
|
224
190
|
current_format[name2sym(f.name)] =
|
@@ -226,7 +192,7 @@ module Phonelib
|
|
226
192
|
end
|
227
193
|
|
228
194
|
current_format
|
229
|
-
end
|
195
|
+
end
|
230
196
|
end
|
231
197
|
|
232
198
|
# method updates data from raw files
|
@@ -245,7 +211,7 @@ module Phonelib
|
|
245
211
|
end
|
246
212
|
|
247
213
|
# method finds country by country prefix
|
248
|
-
def
|
214
|
+
def country_by_code(country_code)
|
249
215
|
match = @data.select { |_k, v| v[:country_code] == country_code }
|
250
216
|
if match.size > 1
|
251
217
|
match = match.select { |_k, v| v[:main_country_for_code] == 'true' }
|
@@ -6,12 +6,38 @@ module Phonelib
|
|
6
6
|
# xml format attributes names
|
7
7
|
XML_FORMAT_NAMES = %w(intlFormat format)
|
8
8
|
|
9
|
+
def file_path(file)
|
10
|
+
"#{File.dirname(__FILE__)}/../../#{file}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# method saves parsed data to data files
|
14
|
+
def save_data_file
|
15
|
+
File.open(file_path(Phonelib::Core::FILE_MAIN_DATA), 'wb+') do |f|
|
16
|
+
Marshal.dump(@data, f)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# method saves extended data file
|
21
|
+
def save_extended_data_file
|
22
|
+
extended = {
|
23
|
+
Phonelib::Core::EXT_PREFIXES => @prefixes,
|
24
|
+
Phonelib::Core::EXT_GEO_NAMES => @geo_names,
|
25
|
+
Phonelib::Core::EXT_TIMEZONES => @timezones,
|
26
|
+
Phonelib::Core::EXT_CARRIERS => @carriers
|
27
|
+
}
|
28
|
+
File.open(file_path(Phonelib::Core::FILE_EXT_DATA), 'wb+') do |f|
|
29
|
+
Marshal.dump(extended, f)
|
30
|
+
end
|
31
|
+
puts 'DATA SAVED'
|
32
|
+
end
|
33
|
+
|
9
34
|
# method updates prefixes hash recursively
|
10
35
|
def fill_prefixes(key, value, prefix, prefixes)
|
11
36
|
prefixes = {} if prefixes.nil?
|
12
37
|
if prefix.size == 1
|
13
|
-
|
14
|
-
prefixes[
|
38
|
+
pr = prefix.to_i
|
39
|
+
prefixes[pr] ||= {}
|
40
|
+
prefixes[pr][key] = value
|
15
41
|
else
|
16
42
|
pr = prefix[0].to_i
|
17
43
|
prefixes[pr] = fill_prefixes(key, value, prefix[1..-1], prefixes[pr])
|
@@ -32,7 +58,7 @@ module Phonelib
|
|
32
58
|
end
|
33
59
|
|
34
60
|
# method creates hash from xml elements/element attributes
|
35
|
-
def
|
61
|
+
def hash_from_xml(data, type)
|
36
62
|
hash = {}
|
37
63
|
case type
|
38
64
|
when :attributes
|
data/lib/phonelib/phone.rb
CHANGED
@@ -42,20 +42,20 @@ module Phonelib
|
|
42
42
|
def sanitized
|
43
43
|
@sanitized ||=
|
44
44
|
vanity_converted(@original).gsub(
|
45
|
-
Phonelib.strict_check ?
|
45
|
+
Phonelib.strict_check ? cr('^\+') : cr('[^0-9]+'),
|
46
46
|
'')
|
47
47
|
end
|
48
48
|
|
49
49
|
# Returns all phone types that matched valid patterns
|
50
50
|
# @return [Array] all valid phone types
|
51
51
|
def types
|
52
|
-
@data.flat_map { |_iso2, data| data[:valid] }.uniq
|
52
|
+
@types ||= @data.flat_map { |_iso2, data| data[:valid] }.uniq
|
53
53
|
end
|
54
54
|
|
55
55
|
# Returns all possible types that matched possible patterns
|
56
56
|
# @return [Array] all possible phone types
|
57
57
|
def possible_types
|
58
|
-
@data.flat_map { |_iso2, data| data[:possible] }.uniq
|
58
|
+
@possible_types ||= @data.flat_map { |_iso2, data| data[:possible] }.uniq
|
59
59
|
end
|
60
60
|
|
61
61
|
# Returns first phone type that matched
|
@@ -105,7 +105,7 @@ module Phonelib
|
|
105
105
|
# Returns whether a current parsed phone number is valid
|
106
106
|
# @return [Boolean] parsed phone is valid
|
107
107
|
def valid?
|
108
|
-
@data.select { |_iso2, data| data[:valid].any? }.any?
|
108
|
+
@valid ||= @data.select { |_iso2, data| data[:valid].any? }.any?
|
109
109
|
end
|
110
110
|
|
111
111
|
# Returns whether a current parsed phone number is invalid
|
@@ -117,7 +117,7 @@ module Phonelib
|
|
117
117
|
# Returns whether a current parsed phone number is possible
|
118
118
|
# @return [Boolean] parsed phone is possible
|
119
119
|
def possible?
|
120
|
-
@data.select { |_iso2, data| data[:possible].any? }.any?
|
120
|
+
@possible ||= @data.select { |_iso2, data| data[:possible].any? }.any?
|
121
121
|
end
|
122
122
|
|
123
123
|
# Returns whether a current parsed phone number is impossible
|
@@ -18,16 +18,16 @@ module Phonelib
|
|
18
18
|
def analyze(phone, passed_country)
|
19
19
|
country = country_or_default_country passed_country
|
20
20
|
|
21
|
-
result =
|
21
|
+
result = parse_country(phone, country)
|
22
22
|
d_result = case
|
23
|
-
when result && result.values.find {|e| e[:valid].any? }
|
23
|
+
when result && result.values.find { |e| e[:valid].any? }
|
24
24
|
# all is good, return result
|
25
25
|
when passed_country.nil?
|
26
26
|
# trying for all countries if no country was passed
|
27
27
|
detect_and_parse phone
|
28
|
-
when
|
28
|
+
when country_can_dp?(country)
|
29
29
|
# if country allows double prefix trying modified phone
|
30
|
-
|
30
|
+
parse_country(changed_dp_phone(country, phone), country)
|
31
31
|
end
|
32
32
|
better_result(result, d_result)
|
33
33
|
end
|
@@ -36,19 +36,14 @@ module Phonelib
|
|
36
36
|
|
37
37
|
# method checks which result is better to return
|
38
38
|
def better_result(base_result, result = nil)
|
39
|
-
|
40
|
-
|
41
|
-
end
|
39
|
+
base_result ||= {}
|
40
|
+
return base_result unless result
|
42
41
|
|
43
|
-
|
44
|
-
return result
|
45
|
-
end
|
42
|
+
return result unless base_result.values.find { |e| e[:possible].any? }
|
46
43
|
|
47
|
-
|
48
|
-
return result
|
49
|
-
end
|
44
|
+
return result if result.values.find { |e| e[:valid].any? }
|
50
45
|
|
51
|
-
base_result
|
46
|
+
base_result
|
52
47
|
end
|
53
48
|
|
54
49
|
# trying to parse phone for single country including international prefix
|
@@ -58,9 +53,9 @@ module Phonelib
|
|
58
53
|
#
|
59
54
|
# * +phone+ - phone for parsing
|
60
55
|
# * +country+ - country to parse phone with
|
61
|
-
def
|
56
|
+
def parse_country(phone, country)
|
62
57
|
data = Phonelib.phone_data[country]
|
63
|
-
return nil unless
|
58
|
+
return nil unless data
|
64
59
|
|
65
60
|
# if country was provided and it's a valid country, trying to
|
66
61
|
# create e164 representation of phone number,
|
@@ -69,7 +64,7 @@ module Phonelib
|
|
69
64
|
# if phone starts with international prefix of provided
|
70
65
|
# country try to reanalyze without international prefix for
|
71
66
|
# all countries
|
72
|
-
return analyze(e164
|
67
|
+
return analyze(e164[1..-1], nil) if Core::PLUS_SIGN == e164[0]
|
73
68
|
# trying to parse number for provided country
|
74
69
|
parse_single_country e164, data
|
75
70
|
end
|
@@ -83,10 +78,10 @@ module Phonelib
|
|
83
78
|
def parse_single_country(e164, data)
|
84
79
|
valid_match = phone_match_data?(e164, data)
|
85
80
|
if valid_match
|
86
|
-
national_and_data(
|
81
|
+
national_and_data(data, valid_match)
|
87
82
|
else
|
88
83
|
possible_match = phone_match_data?(e164, data, true)
|
89
|
-
possible_match && national_and_data(
|
84
|
+
possible_match && national_and_data(data, possible_match, true)
|
90
85
|
end
|
91
86
|
end
|
92
87
|
|
@@ -100,7 +95,7 @@ module Phonelib
|
|
100
95
|
Phonelib.phone_data.each do |key, data|
|
101
96
|
parsed = parse_single_country(phone, data)
|
102
97
|
if double_prefix_allowed?(data, phone, parsed && parsed[key])
|
103
|
-
parsed = parse_single_country(
|
98
|
+
parsed = parse_single_country(changed_dp_phone(key, phone), data)
|
104
99
|
end
|
105
100
|
result.merge!(parsed) unless parsed.nil?
|
106
101
|
end
|
@@ -114,13 +109,14 @@ module Phonelib
|
|
114
109
|
# * +phone+ - phone number for parsing
|
115
110
|
# * +data+ - country data to be based on for creating e164 representation
|
116
111
|
def convert_to_e164(phone, data)
|
117
|
-
match = phone.match full_regex_for_data(data, Core::VALID_PATTERN)
|
112
|
+
match = phone.match full_regex_for_data(data, Core::VALID_PATTERN, !original_starts_with_plus?)
|
118
113
|
case
|
119
114
|
when match
|
120
|
-
|
121
|
-
"#{data[Core::COUNTRY_CODE]}#{phone[national_start..-1]}"
|
115
|
+
"#{data[Core::COUNTRY_CODE]}#{match.to_a.last}"
|
122
116
|
when phone.match(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"))
|
123
|
-
phone.sub(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"),
|
117
|
+
phone.sub(cr("^#{data[Core::INTERNATIONAL_PREFIX]}"), Core::PLUS_SIGN)
|
118
|
+
when original_starts_with_plus? && phone.start_with?(data[Core::COUNTRY_CODE])
|
119
|
+
phone
|
124
120
|
else
|
125
121
|
"#{data[Core::COUNTRY_CODE]}#{phone}"
|
126
122
|
end
|
@@ -130,18 +126,15 @@ module Phonelib
|
|
130
126
|
#
|
131
127
|
# ==== Attributes
|
132
128
|
#
|
133
|
-
# * +phone+ - phone number for parsing
|
134
129
|
# * +data+ - country data
|
135
130
|
# * +country_match+ - result of match of phone within full regex
|
136
131
|
# * +not_valid+ - specifies that number is not valid by general desc pattern
|
137
|
-
def national_and_data(
|
138
|
-
prefix_length = data[Core::COUNTRY_CODE].length
|
139
|
-
prefix_length += [1, 2].map { |i| country_match[i].to_s.size }.inject(:+)
|
132
|
+
def national_and_data(data, country_match, not_valid = false)
|
140
133
|
result = data.select { |k, _v| k != :types && k != :formats }
|
141
|
-
|
142
|
-
result[:
|
143
|
-
result
|
144
|
-
|
134
|
+
phone = country_match.to_a.last
|
135
|
+
result[:national] = phone
|
136
|
+
result[:format] = number_format(phone, data[Core::FORMATS])
|
137
|
+
result.merge! all_number_types(phone, data[Core::TYPES], not_valid)
|
145
138
|
result[:valid] = [] if not_valid
|
146
139
|
|
147
140
|
{ result[:id] => result }
|
@@ -178,9 +171,9 @@ module Phonelib
|
|
178
171
|
# * +format_data+ - formatting data from country data
|
179
172
|
def number_format(national, format_data)
|
180
173
|
format_data && format_data.find do |format|
|
181
|
-
(format[Core::LEADING_DIGITS].nil? \
|
182
|
-
|
183
|
-
|
174
|
+
(format[Core::LEADING_DIGITS].nil? || \
|
175
|
+
national.match(cr("^(#{format[Core::LEADING_DIGITS]})"))) && \
|
176
|
+
national.match(cr("^(#{format[Core::PATTERN]})$"))
|
184
177
|
end || Core::DEFAULT_NUMBER_FORMAT
|
185
178
|
end
|
186
179
|
|
@@ -3,24 +3,28 @@ module Phonelib
|
|
3
3
|
module PhoneAnalyzerHelper
|
4
4
|
private
|
5
5
|
|
6
|
+
def original_starts_with_plus?
|
7
|
+
original_s[0] == Core::PLUS_SIGN
|
8
|
+
end
|
9
|
+
|
10
|
+
# converts symbols in phone to numbers
|
6
11
|
def vanity_converted(phone)
|
7
12
|
return phone unless Phonelib.vanity_conversion
|
8
13
|
|
9
|
-
(phone || '').gsub(
|
14
|
+
(phone || '').gsub(cr('[a-zA-Z]')) do |c|
|
10
15
|
c.upcase!
|
11
16
|
# subtract "A"
|
12
17
|
n = (c.ord - 65) / 3
|
13
18
|
# account for #7 & #9 which have 4 chars
|
14
|
-
n -= 1 if c
|
19
|
+
n -= 1 if c.match(Core::VANITY_4_LETTERS_KEYS_REGEX)
|
15
20
|
(n + 2).to_s
|
16
21
|
end
|
17
22
|
end
|
18
23
|
|
19
|
-
|
20
24
|
# defines if to validate against single country or not
|
21
25
|
def passed_country(country)
|
22
26
|
code = country_prefix(country)
|
23
|
-
if @original
|
27
|
+
if Core::PLUS_SIGN == @original[0] && code && !sanitized.start_with?(code)
|
24
28
|
# in case number passed with + but it doesn't start with passed
|
25
29
|
# country prefix
|
26
30
|
country = nil
|
@@ -37,17 +41,18 @@ module Phonelib
|
|
37
41
|
|
38
42
|
# caches regular expression, reusing it for later lookups
|
39
43
|
def cr(regexp)
|
40
|
-
Phonelib.phone_regexp_cache[regexp] ||= Regexp.new(regexp)
|
44
|
+
Phonelib.phone_regexp_cache[regexp] ||= Regexp.new(regexp).freeze
|
41
45
|
end
|
42
46
|
|
43
47
|
# defines whether country can have double country prefix in number
|
44
|
-
def
|
48
|
+
def country_can_dp?(country)
|
45
49
|
Phonelib.phone_data[country] &&
|
46
|
-
Phonelib.phone_data[country][Core::DOUBLE_COUNTRY_PREFIX_FLAG]
|
50
|
+
Phonelib.phone_data[country][Core::DOUBLE_COUNTRY_PREFIX_FLAG] &&
|
51
|
+
!original_starts_with_plus?
|
47
52
|
end
|
48
53
|
|
49
54
|
# changes phone to with/without double country prefix
|
50
|
-
def
|
55
|
+
def changed_dp_phone(country, phone)
|
51
56
|
data = Phonelib.phone_data[country]
|
52
57
|
return if data.nil? || data[Core::DOUBLE_COUNTRY_PREFIX_FLAG].nil?
|
53
58
|
|
@@ -67,14 +72,15 @@ module Phonelib
|
|
67
72
|
# * +phone+ - phone number being parsed
|
68
73
|
# * +parsed+ - parsed regex match for phone
|
69
74
|
def double_prefix_allowed?(data, phone, parsed = {})
|
70
|
-
data
|
75
|
+
data[Core::DOUBLE_COUNTRY_PREFIX_FLAG] &&
|
71
76
|
phone =~ cr("^#{data[Core::COUNTRY_CODE]}") &&
|
72
|
-
parsed && (parsed[:valid].nil? || parsed[:valid].empty?)
|
77
|
+
parsed && (parsed[:valid].nil? || parsed[:valid].empty?) &&
|
78
|
+
!original_starts_with_plus?
|
73
79
|
end
|
74
80
|
|
75
81
|
# Returns original number passed if it's a string or empty string otherwise
|
76
|
-
def
|
77
|
-
@original.is_a?(String) ? @original : ''
|
82
|
+
def original_s
|
83
|
+
@original_s ||= @original.is_a?(String) ? @original : ''
|
78
84
|
end
|
79
85
|
|
80
86
|
# Get country that was provided or default country in needable format
|
@@ -83,7 +89,7 @@ module Phonelib
|
|
83
89
|
#
|
84
90
|
# * +country+ - country passed for parsing
|
85
91
|
def country_or_default_country(country)
|
86
|
-
country ||= (
|
92
|
+
country ||= (original_starts_with_plus? ? nil : Phonelib.default_country)
|
87
93
|
country && country.to_s.upcase
|
88
94
|
end
|
89
95
|
|
@@ -97,11 +103,7 @@ module Phonelib
|
|
97
103
|
def full_regex_for_data(data, type, country_optional = true)
|
98
104
|
regex = []
|
99
105
|
regex << "(#{data[Core::INTERNATIONAL_PREFIX]})?"
|
100
|
-
regex <<
|
101
|
-
"(#{data[Core::COUNTRY_CODE]})?"
|
102
|
-
else
|
103
|
-
data[Core::COUNTRY_CODE]
|
104
|
-
end
|
106
|
+
regex << "(#{data[Core::COUNTRY_CODE]})#{country_optional ? '?' : ''}"
|
105
107
|
regex << "(#{data[Core::NATIONAL_PREFIX_FOR_PARSING] || data[Core::NATIONAL_PREFIX]})?"
|
106
108
|
regex << "(#{data[Core::TYPES][Core::GENERAL][type]})"
|
107
109
|
|
@@ -165,22 +167,27 @@ module Phonelib
|
|
165
167
|
# ==== Attributes
|
166
168
|
#
|
167
169
|
# * +number+ - phone number for validation
|
168
|
-
# * +
|
169
|
-
# * +
|
170
|
+
# * +p_regex+ - possible regex pattern for validation
|
171
|
+
# * +v_regex+ - valid regex pattern for validation
|
170
172
|
# * +not_valid+ - specifies that number is not valid by general desc pattern
|
171
|
-
def number_valid_and_possible?(number,
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
valid = false
|
177
|
-
if !not_valid && possible
|
178
|
-
# doing national pattern match only in case possible matches
|
179
|
-
national_match = number.match(cr("^(?:#{national_pattern})$"))
|
180
|
-
valid = national_match && national_match.to_s.length == number.length
|
181
|
-
end
|
173
|
+
def number_valid_and_possible?(number, p_regex, v_regex, not_valid = false)
|
174
|
+
possible = number_match?(number, p_regex)
|
175
|
+
|
176
|
+
return [!not_valid && possible, possible] if p_regex == v_regex
|
177
|
+
valid = !not_valid && possible && number_match?(number, v_regex)
|
182
178
|
|
183
179
|
[valid && possible, possible]
|
184
180
|
end
|
181
|
+
|
182
|
+
# Checks number against regex and compares match length
|
183
|
+
#
|
184
|
+
# ==== Attributes
|
185
|
+
#
|
186
|
+
# * +number+ - phone number for validation
|
187
|
+
# * +regex+ - regex for perfoming a validation
|
188
|
+
def number_match?(number, regex)
|
189
|
+
match = number.match(cr("^(?:#{regex})$"))
|
190
|
+
match && match.to_s.length == number.length
|
191
|
+
end
|
185
192
|
end
|
186
193
|
end
|
@@ -32,24 +32,23 @@ module Phonelib
|
|
32
32
|
# Returns the country code from the original phone number.
|
33
33
|
# @return [String] matched country phone code
|
34
34
|
def country_code
|
35
|
-
Phonelib.phone_data[country] && \
|
36
|
-
|
35
|
+
@country_code ||= Phonelib.phone_data[country] && \
|
36
|
+
Phonelib.phone_data[country][Core::COUNTRY_CODE]
|
37
37
|
end
|
38
38
|
|
39
39
|
# Returns e164 formatted phone number
|
40
40
|
# @param formatted [Boolean] whether to return numbers only or formatted
|
41
41
|
# @return [String] formatted international number
|
42
42
|
def international(formatted = true)
|
43
|
-
return nil if sanitized.
|
43
|
+
return nil if sanitized.empty?
|
44
44
|
return "+#{country_prefix_or_not}#{sanitized}" unless valid?
|
45
|
-
return
|
45
|
+
return country_code + @national_number unless formatted
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
fmt = @data[country][:format]
|
48
|
+
national = @national_number
|
49
|
+
if (matches = @national_number.match(cr(fmt[Core::PATTERN])))
|
50
|
+
fmt = fmt[:intl_format] || fmt[:format]
|
50
51
|
national = fmt.gsub(/\$\d/) { |el| matches[el[1].to_i] }
|
51
|
-
else
|
52
|
-
national = @national_number
|
53
52
|
end
|
54
53
|
|
55
54
|
"+#{country_code} #{national}"
|
@@ -88,7 +87,7 @@ module Phonelib
|
|
88
87
|
format_match, _format_string = formatting_data
|
89
88
|
take_group = 1
|
90
89
|
if type == Core::MOBILE && Core::AREA_CODE_MOBILE_TOKENS[country] && \
|
91
|
-
|
90
|
+
format_match[1] == Core::AREA_CODE_MOBILE_TOKENS[country]
|
92
91
|
take_group = 2
|
93
92
|
end
|
94
93
|
format_match[take_group]
|
@@ -98,7 +97,7 @@ module Phonelib
|
|
98
97
|
|
99
98
|
# @private defines if phone can have area code
|
100
99
|
def area_code_possible?
|
101
|
-
return false
|
100
|
+
return false if impossible?
|
102
101
|
|
103
102
|
# has national prefix
|
104
103
|
return false unless @data[country][Core::NATIONAL_PREFIX] || country == 'IT'
|
@@ -126,10 +125,11 @@ module Phonelib
|
|
126
125
|
def formatting_data
|
127
126
|
return @formatting_data if defined?(@formatting_data)
|
128
127
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
128
|
+
data = @data[country]
|
129
|
+
format = data[:format]
|
130
|
+
prefix = data[Core::NATIONAL_PREFIX]
|
131
|
+
rule = format[Core::NATIONAL_PREFIX_RULE] ||
|
132
|
+
data[Core::NATIONAL_PREFIX_RULE] || '$1'
|
133
133
|
|
134
134
|
# change rule's constants to values
|
135
135
|
rule.gsub!(/(\$NP|\$FG)/, '$NP' => prefix, '$FG' => '$1')
|
data/lib/phonelib/version.rb
CHANGED
@@ -43,21 +43,32 @@ class PhoneValidator < ActiveModel::EachValidator
|
|
43
43
|
# Validation method
|
44
44
|
def validate_each(record, attribute, value)
|
45
45
|
return if options[:allow_blank] && value.blank?
|
46
|
-
country = options[:country_specifier].call(record) if options[:country_specifier]
|
47
46
|
|
48
|
-
phone = parse(value,
|
47
|
+
phone = parse(value, specified_country(record))
|
49
48
|
valid = if simple_validation?
|
50
|
-
|
51
|
-
phone.send(method)
|
49
|
+
phone.send(validate_method)
|
52
50
|
else
|
53
51
|
(phone_types(phone) & types).size > 0
|
54
52
|
end
|
55
53
|
|
56
|
-
record.errors.add(attribute,
|
54
|
+
record.errors.add(attribute, message, options) unless valid
|
57
55
|
end
|
58
56
|
|
59
57
|
private
|
60
58
|
|
59
|
+
def message
|
60
|
+
options[:message] || :invalid
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_method
|
64
|
+
options[:possible] ? :possible? : :valid?
|
65
|
+
end
|
66
|
+
|
67
|
+
def specified_country(record)
|
68
|
+
return unless options[:country_specifier]
|
69
|
+
options[:country_specifier].call(record)
|
70
|
+
end
|
71
|
+
|
61
72
|
# @private
|
62
73
|
def simple_validation?
|
63
74
|
options[:types].nil?
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phonelib
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vadim Senderovich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|