phony 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ # Splits a national number using a prefix code.
2
+ #
3
+ # DSL:
4
+ # length: the length range of the dcs. For austria it is 1..4.
5
+ # format: the format of the dcs and rest. For austria it is '%s %s', default is '%s %s %s %s'.
6
+ # ndcs: an array of the prefix codes. For germany it starts with ['10', '11', '12', '13', '30'…
7
+ #
8
+ module Phony
9
+ module NDC
10
+ class Prefix < Splitter
11
+
12
+ # Splits the number into ndc and rest.
13
+ #
14
+ def self.split_ndc(number)
15
+ number = number.dup
16
+ presumed_code = ''
17
+ presumed_code << number.slice!(0..@min_ndc_length-2) if @min_ndc_length && @min_ndc_length > 1
18
+ (@min_ndc_length || 1).upto(@max_ndc_length) do |i|
19
+ presumed_code << number.slice!(0..0)
20
+ sized_ndcs = @ndcs[i]
21
+ break unless sized_ndcs && !sized_ndcs.include?(presumed_code)
22
+ end
23
+ return [presumed_code, number]
24
+ end
25
+
26
+ protected
27
+
28
+ # Define the length range for the country's ndcs.
29
+ #
30
+ def self.length(range)
31
+ @min_ndc_length, @max_ndc_length = range.min, range.max
32
+ end
33
+
34
+ # Define the list of NDCs.
35
+ #
36
+ # Note: For optimization, if the country has a max ndc length of 4,
37
+ # only enter ndcs up to the length of 3 letters.
38
+ # If the algorithm fails to match an ndc, it will assume it is a 4 digit ndc.
39
+ #
40
+ # e.g. We have NDCs '1', '22', '333', and '4444'.
41
+ # Only set
42
+ # ndcs '1', '22', '333'
43
+ # and
44
+ # length 1..4
45
+ # '4444' will be "recognized" because the algorithm does not find a shorter ndc (assuming it
46
+ # is a correct prefix code) and stop at the max ndc length of 4, given by the length method.
47
+ #
48
+ def self.ndcs(*ndcs_ary)
49
+ @ndcs = optimize(ndcs_ary)
50
+ end
51
+
52
+ private
53
+
54
+ # Optimizes and restructures the given ndcs array.
55
+ #
56
+ def self.optimize(ndcs_ary)
57
+ ndcs = {}
58
+ ndcs_ary.each do |ndc|
59
+ ndcs[ndc.length] ||= []
60
+ ndcs[ndc.length] << ndc
61
+ end
62
+ ndcs
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,81 @@
1
+ # Splits a national number into a fixed size NDC and rest.
2
+ #
3
+ module Phony
4
+ module NDC
5
+ class Splitter
6
+
7
+ # Sets the local grouping format.
8
+ #
9
+ # Examples
10
+ # * local 3, 2, 2 # Switzerland, 364 35 33, thus: 3-2-2.
11
+ # * local 2, 2, 2, 2 # France, 12 34 56 78, thus: 2-2-2-2.
12
+ #
13
+ def self.local *split_sizes
14
+ @split_sizes = split_sizes.flatten
15
+ format((['%s']*@split_sizes.size).join('%s'))
16
+ end
17
+
18
+ # Define a format for the country's local format.
19
+ #
20
+ # Examples
21
+ # * format '%s%s%s%s%s' # Switzerland, spaces between the groups, e.g. 364˽35˽32
22
+ #
23
+ def self.format format
24
+ @format = format
25
+ @format_with_ndc = ndc_format + @format
26
+ end
27
+
28
+ # Define a format for the country's national format.
29
+ #
30
+ # Default is NN followed by a space character.
31
+ #
32
+ def self.ndc_format
33
+ '%s%s'
34
+ end
35
+
36
+ # Split an E164 number without country code into its NDC-Local parts.
37
+ #
38
+ # Examples
39
+ # * split '443643533' # => ['44', '364', '35', '33'] # (Switzerland)
40
+ #
41
+ def self.split number
42
+ ndc_part = split_ndc(number)
43
+ [ndc_part, split_local(ndc_part.pop)].flatten
44
+ end
45
+
46
+ # Split a local number according to an assumed country specific format.
47
+ #
48
+ # Examples
49
+ # * split '3643533' # => ['364', '35', '33'] # (Switzerland)
50
+ #
51
+ def self.split_local number
52
+ local = []
53
+ @split_sizes.each do |size|
54
+ local << number.slice!(0..size-1)
55
+ break if number.empty?
56
+ end
57
+ local
58
+ end
59
+
60
+ # Formats the given E164 Number (NDC-Local without CC) according to the country specific format / ndcs splitting.
61
+ #
62
+ def self.formatted number, space = ' '
63
+ formatted_with_spaces @format_with_ndc, split(number), space
64
+ end
65
+
66
+ # Formats the given local number according to the country specific format.
67
+ #
68
+ def self.locally_formatted number, space = ' '
69
+ formatted_with_spaces @format, split_local(number), space
70
+ end
71
+
72
+ def self.formatted_with_spaces format, split_number, space
73
+ space ||= ' '
74
+ number_and_spaces = (split_number).zip([space]*split_number.size).flatten
75
+ format % number_and_spaces
76
+ end
77
+
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,496 @@
1
+ begin
2
+ gem 'activesupport', '~> 3.0'
3
+ rescue LoadError
4
+ puts "phony requires activesupport ~> 3.0"
5
+ end
6
+
7
+ require 'active_support/core_ext/object/blank'
8
+
9
+ # Framework.
10
+ #
11
+ require File.expand_path '../ndc/splitter', __FILE__
12
+ require File.expand_path '../ndc/fixed_size', __FILE__
13
+ require File.expand_path '../ndc/prefix', __FILE__
14
+
15
+ # Prefix code countries
16
+ #
17
+ require File.expand_path '../ndc/austria', __FILE__
18
+ require File.expand_path '../ndc/germany', __FILE__
19
+
20
+ # require 'Phony'
21
+
22
+ module Phony
23
+
24
+ include NDC
25
+
26
+ # Returns a fixed ndc splitter.
27
+ #
28
+ def self.fixed national_code_length = 2, options = {}
29
+ NDC.fixed national_code_length, options
30
+ end
31
+
32
+ # prefix length =>
33
+ # country code => national prefix length
34
+ @@country_codes = {
35
+ 1 => {
36
+ '0' => fixed(0), # reserved
37
+ '1' => fixed(3, :local => [3, 10]), # USA, Canada, etc.
38
+ '7' => fixed(3), # Kazakhstan (Republic of) & Russian Federation
39
+ },
40
+ 2 => {
41
+ '20' => fixed(2), # Egypt
42
+ '27' => fixed(2), # South Africa
43
+
44
+ '30' => fixed(2), # Greece
45
+ '31' => fixed(2), # Netherlands
46
+ '32' => fixed(2), # Belgium
47
+ '33' => fixed(1, :local => [2, 2, 2, 2]), # France
48
+ '34' => fixed(2), # Spain
49
+ '36' => fixed(2), # Hungary
50
+ '39' => fixed(3), # Italy
51
+
52
+ '40' => fixed(2), # Romania
53
+ '41' => fixed(2, :local => [3, 2, 2], :special_ndcs => ['800', '840', '842', '844', '848']), # Switzerland (Confederation of)
54
+ '43' => Austria,
55
+ '44' => fixed(2), # United Kingdom of Great Britain and Northern Ireland
56
+ '45' => fixed(2), # Denmark
57
+ '46' => fixed(2), # Sweden
58
+ '47' => fixed(2), # Norway
59
+ '48' => fixed(2), # Poland (Republic of)
60
+ '49' => Germany,
61
+
62
+ '51' => fixed(2), # Peru
63
+ '52' => fixed(2), # Mexico
64
+ '53' => fixed(2), # Cuba
65
+ '54' => fixed(2), # Argentine Republic
66
+ '55' => fixed(2), # Brazil (Federative Republic of)
67
+ '56' => fixed(2), # Chile
68
+ '57' => fixed(2), # Colombia (Republic of)
69
+ '58' => fixed(2), # Venezuela (Bolivarian Republic of)
70
+
71
+ '60' => fixed(2), # Malaysia
72
+ '61' => fixed(2), # Australia
73
+ '62' => fixed(2), # Indonesia (Republic of)
74
+ '63' => fixed(2), # Philippines (Republic of the)
75
+ '64' => fixed(1, :local => [3, 4]), # New Zealand
76
+ '65' => fixed(2), # Singapore (Republic of)
77
+ '66' => fixed(2), # Thailand
78
+
79
+ '81' => fixed(2), # Japan
80
+ '82' => fixed(2), # Korea (Republic of)
81
+ '84' => fixed(2), # Viet Nam (Socialist Republic of)
82
+ '86' => fixed(2), # China (People's Republic of)
83
+
84
+ '90' => fixed(2), # Turkey
85
+ '91' => fixed(2), # India (Republic of)
86
+ '92' => fixed(2), # Pakistan (Islamic Republic of)
87
+ '93' => fixed(2), # Afghanistan
88
+ '94' => fixed(2), # Sri Lanka (Democratic Socialist Republic of)
89
+ '95' => fixed(2), # Myanmar (Union of)
90
+ '98' => fixed(2), #Iran (Islamic Republic of)
91
+ },
92
+ 3 => {
93
+ '210' => fixed(2), # -
94
+ '211' => fixed(2), # -
95
+ '212' => fixed(2), # Morocco
96
+ '213' => fixed(2), # Algeria
97
+ '214' => fixed(2), # -
98
+ '215' => fixed(2), # -
99
+ '216' => fixed(2), # Tunisia
100
+ '217' => fixed(2), # -
101
+ '218' => fixed(2), # Lybia
102
+ '219' => fixed(2), # -
103
+
104
+ '220' => fixed(2), # Gambia
105
+ '221' => fixed(2), # Senegal
106
+ '222' => fixed(2), # Mauritania
107
+ '223' => fixed(2), # Mali
108
+ '224' => fixed(2), # Guinea
109
+ '225' => fixed(2), # Côte d'Ivoire
110
+ '226' => fixed(2), # Burkina Faso
111
+ '227' => fixed(2), # Niger
112
+ '228' => fixed(2), # Togolese Republic
113
+ '229' => fixed(2), # Benin
114
+
115
+ '230' => fixed(2), # Mauritius
116
+ '231' => fixed(2), # Liberia
117
+ '232' => fixed(2), # Sierra Leone
118
+ '233' => fixed(2), # Ghana
119
+ '234' => fixed(2), # Nigeria
120
+ '235' => fixed(2), # Chad
121
+ '236' => fixed(2), # Central African Republic
122
+ '237' => fixed(2), # Cameroon
123
+ '238' => fixed(2), # Cape Verde
124
+ '239' => fixed(2), # Sao Tome and Principe
125
+
126
+ '240' => fixed(2), # Equatorial Guinea
127
+ '241' => fixed(2), # Gabonese Republic
128
+ '242' => fixed(2), # Congo
129
+ '243' => fixed(2), # Democratic Republic of the Congo
130
+ '244' => fixed(2), # Angola
131
+ '245' => fixed(2), # Guinea-Bissau
132
+ '246' => fixed(2), # Diego Garcia
133
+ '247' => fixed(2), # Ascension
134
+ '248' => fixed(2), # Seychelles
135
+ '249' => fixed(2), # Sudan
136
+
137
+ '250' => fixed(2), # Rwanda
138
+ '251' => fixed(2), # Ethiopia
139
+ '252' => fixed(2), # Somali Democratic Republic
140
+ '253' => fixed(2), # Djibouti
141
+ '254' => fixed(2), # Kenya
142
+ '255' => fixed(2), # Tanzania
143
+ '256' => fixed(2), # Uganda
144
+ '257' => fixed(2), # Burundi
145
+ '258' => fixed(2), # Mozambique
146
+ '259' => fixed(2), # -
147
+
148
+ '260' => fixed(2), # Zambia
149
+ '261' => fixed(2), # Madagascar
150
+ '262' => fixed(3), # Reunion / Mayotte (new)
151
+ '263' => fixed(2), # Zimbabwe
152
+ '264' => fixed(2), # Namibia
153
+ '265' => fixed(2), # Malawi
154
+ '266' => fixed(2), # Lesotho
155
+ '267' => fixed(2), # Botswana
156
+ '268' => fixed(2), # Swaziland
157
+ '269' => fixed(2), # Comoros
158
+
159
+ '280' => fixed(2), # -
160
+ '281' => fixed(2), # -
161
+ '282' => fixed(2), # -
162
+ '283' => fixed(2), # -
163
+ '284' => fixed(2), # -
164
+ '285' => fixed(2), # -
165
+ '286' => fixed(2), # -
166
+ '287' => fixed(2), # -
167
+ '288' => fixed(2), # -
168
+ '289' => fixed(2), # -
169
+ '290' => fixed(2), # Saint Helena
170
+
171
+ '291' => fixed(2), # Eritrea
172
+ '292' => fixed(2), # -
173
+ '293' => fixed(2), # -
174
+ '294' => fixed(2), # -
175
+ '295' => fixed(2), # -
176
+ '296' => fixed(2), # -
177
+ '297' => fixed(2), # Aruba
178
+ '298' => fixed(2), # Faroe Islands
179
+ '299' => fixed(2), # Greenland
180
+
181
+ '350' => fixed(2), # Gibraltar
182
+ '351' => fixed(2), # Portugal
183
+ '352' => fixed(2), # Luxembourg
184
+ '353' => fixed(2), # Ireland
185
+ '354' => fixed(2), # Iceland
186
+ '355' => fixed(2), # Albania
187
+ '356' => fixed(2), # Malta
188
+ '357' => fixed(2), # Cyprus
189
+ '358' => fixed(2), # Finland
190
+ '359' => fixed(2), # Bulgaria
191
+
192
+ '370' => fixed(2), # Lithuania
193
+ '371' => fixed(2), # Latvia
194
+ '372' => fixed(2), # Estonia
195
+ '373' => fixed(2), # Moldova
196
+ '374' => fixed(2), # Armenia
197
+ '375' => fixed(2), # Belarus
198
+ '376' => fixed(2), # Andorra
199
+ '377' => fixed(2), # Monaco
200
+ '378' => fixed(2), # San Marino
201
+ '379' => fixed(2), # Vatican City State
202
+
203
+ '380' => fixed(2), # Ukraine
204
+ '381' => fixed(2), # Serbia and Montenegro
205
+ '382' => fixed(2), # -
206
+ '383' => fixed(2), # -
207
+ '384' => fixed(2), # -
208
+ '385' => fixed(2), # Croatia
209
+ '386' => fixed(2), # Slovenia
210
+ '387' => fixed(2), # Bosnia and Herzegovina
211
+ '388' => fixed(2), # Group of countries, shared code
212
+ '389' => fixed(2), # The Former Yugoslav Republic of Macedonia
213
+
214
+ '420' => fixed(2), # Czech Republic
215
+ '421' => fixed(2), # Slovak Republic
216
+ '422' => fixed(2), # Spare code
217
+ '423' => fixed(0, :local => [3, 2, 2]), # Liechtenstein (Principality of)
218
+ '424' => fixed(2), # -
219
+ '425' => fixed(2), # -
220
+ '426' => fixed(2), # -
221
+ '427' => fixed(2), # -
222
+ '428' => fixed(2), # -
223
+ '429' => fixed(2), # -
224
+
225
+ '500' => fixed(2), # Falkland Islands (Malvinas)
226
+ '501' => fixed(2), # Belize
227
+ '502' => fixed(2), # Guatemala (Republic of)
228
+ '503' => fixed(2), # El Salvador (Republic of)
229
+ '504' => fixed(2), # Honduras (Republic of)
230
+ '505' => fixed(2), # Nicaragua
231
+ '506' => fixed(2), # Costa Rica
232
+ '507' => fixed(2), # Panama (Republic of)
233
+ '508' => fixed(2), # Saint Pierre and Miquelon (Collectivité territoriale de la République française)
234
+ '509' => fixed(2), # Haiti (Republic of)
235
+
236
+ '590' => fixed(3), # Guadeloupe (French Department of)
237
+ '591' => fixed(2), # Bolivia (Republic of)
238
+ '592' => fixed(2), # Guyana
239
+ '593' => fixed(2), # Ecuador
240
+ '594' => fixed(3), # French Guiana (French Department of)
241
+ '595' => fixed(2), # Paraguay (Republic of)
242
+ '596' => fixed(3), # Martinique (French Department of)
243
+ '597' => fixed(2), # Suriname (Republic of)
244
+ '598' => fixed(2), # Uruguay (Eastern Republic of)
245
+ '599' => fixed(2), # Netherlands Antilles
246
+
247
+ '670' => fixed(2), # Democratic Republic of Timor-Leste
248
+ '671' => fixed(2), # Spare code
249
+ '672' => fixed(2), # Australian External Territories
250
+ '673' => fixed(2), # Brunei Darussalam
251
+ '674' => fixed(2), # Nauru (Republic of)
252
+ '675' => fixed(2), # Papua New Guinea
253
+ '676' => fixed(2), # Tonga (Kingdom of)
254
+ '677' => fixed(2), # Solomon Islands
255
+ '678' => fixed(2), # Vanuatu (Republic of)
256
+ '679' => fixed(2), # Fiji (Republic of)
257
+
258
+ '680' => fixed(2), # Palau (Republic of)
259
+ '681' => fixed(2), # Wallis and Futuna (Territoire français d'outre-mer)
260
+ '682' => fixed(2), # Cook Islands
261
+ '683' => fixed(2), # Niue
262
+ '684' => fixed(2), # -
263
+ '685' => fixed(2), # Samoa (Independent State of)
264
+ '686' => fixed(2), # Kiribati (Republic of)
265
+ '687' => fixed(2), # New Caledonia (Territoire français d'outre-mer)
266
+ '688' => fixed(2), # Tuvalu
267
+ '689' => fixed(2), # French Polynesia (Territoire français d'outre-mer)
268
+
269
+ '690' => fixed(2), # Tokelau
270
+ '691' => fixed(2), # Micronesia (Federated States of)
271
+ '692' => fixed(2), # Marshall Islands (Republic of the)
272
+ '693' => fixed(2), # -
273
+ '694' => fixed(2), # -
274
+ '695' => fixed(2), # -
275
+ '696' => fixed(2), # -
276
+ '697' => fixed(2), # -
277
+ '698' => fixed(2), # -
278
+ '699' => fixed(2), # -
279
+
280
+ '800' => fixed(2), # International Freephone Service
281
+ '801' => fixed(2), # -
282
+ '802' => fixed(2), # -
283
+ '803' => fixed(2), # -
284
+ '804' => fixed(2), # -
285
+ '805' => fixed(2), # -
286
+ '806' => fixed(2), # -
287
+ '807' => fixed(2), # -
288
+ '808' => fixed(2), # International Shared Cost Service (ISCS)
289
+ '809' => fixed(2), # -
290
+
291
+ '830' => fixed(2), # -
292
+ '831' => fixed(2), # -
293
+ '832' => fixed(2), # -
294
+ '833' => fixed(2), # -
295
+ '834' => fixed(2), # -
296
+ '835' => fixed(2), # -
297
+ '836' => fixed(2), # -
298
+ '837' => fixed(2), # -
299
+ '838' => fixed(2), # -
300
+ '839' => fixed(2), # -
301
+
302
+ '850' => fixed(2), # Democratic People's Republic of Korea
303
+ '851' => fixed(2), # Spare code
304
+ '852' => fixed(2), # Hong Kong, China
305
+ '853' => fixed(2), # Macao, China
306
+ '854' => fixed(2), # Spare code
307
+ '855' => fixed(2), # Cambodia (Kingdom of)
308
+ '856' => fixed(2), # Lao People's Democratic Republic
309
+ '857' => fixed(2), # Spare code
310
+ '858' => fixed(2), # Spare code
311
+ '859' => fixed(2), # Spare code
312
+
313
+ '870' => fixed(2), # Inmarsat SNAC
314
+ '871' => fixed(2), # Inmarsat (Atlantic Ocean-East)
315
+ '872' => fixed(2), # Inmarsat (Pacific Ocean)
316
+ '873' => fixed(2), # Inmarsat (Indian Ocean)
317
+ '874' => fixed(2), # Inmarsat (Atlantic Ocean-West)
318
+ '875' => fixed(2), # Reserved - Maritime Mobile Service Applications
319
+ '876' => fixed(2), # Reserved - Maritime Mobile Service Applications
320
+ '877' => fixed(2), # Reserved - Maritime Mobile Service Applications
321
+ '878' => fixed(2), # Universal Personal Telecommunication Service (UPT)
322
+ '879' => fixed(2), # Reserved for national non-commercial purposes
323
+
324
+ '880' => fixed(2), # Bangladesh (People's Republic of)
325
+ '881' => fixed(2), # International Mobile, shared code
326
+ '882' => fixed(2), # International Networks, shared code
327
+ '883' => fixed(2), # -
328
+ '884' => fixed(2), # -
329
+ '885' => fixed(2), # -
330
+ '886' => fixed(2), # Reserved
331
+ '887' => fixed(2), # -
332
+ '888' => fixed(2), # Reserved for future global service
333
+ '889' => fixed(2), # -
334
+
335
+ '890' => fixed(2), # -
336
+ '891' => fixed(2), # -
337
+ '892' => fixed(2), # -
338
+ '893' => fixed(2), # -
339
+ '894' => fixed(2), # -
340
+ '895' => fixed(2), # -
341
+ '896' => fixed(2), # -
342
+ '897' => fixed(2), # -
343
+ '898' => fixed(2), # -
344
+ '899' => fixed(2), # -
345
+
346
+ '960' => fixed(2), # Maldives (Republic of)
347
+ '961' => fixed(2), # Lebanon
348
+ '962' => fixed(2), # Jordan (Hashemite Kingdom of)
349
+ '963' => fixed(2), # Syrian Arab Republic
350
+ '964' => fixed(2), # Iraq (Republic of)
351
+ '965' => fixed(2), # Kuwait (State of)
352
+ '966' => fixed(2), # Saudi Arabia (Kingdom of)
353
+ '967' => fixed(2), # Yemen (Republic of)
354
+ '968' => fixed(2), # Oman (Sultanate of)
355
+ '969' => fixed(2), # Reserved - reservation currently under investigation
356
+
357
+ '970' => fixed(2), # Reserved
358
+ '971' => fixed(2), # United Arab Emirates
359
+ '972' => fixed(2), # Israel (State of)
360
+ '973' => fixed(2), # Bahrain (Kingdom of)
361
+ '974' => fixed(2), # Qatar (State of)
362
+ '975' => fixed(2), # Bhutan (Kingdom of)
363
+ '976' => fixed(2), # Mongolia
364
+ '977' => fixed(2), # Nepal
365
+ '978' => fixed(2), # -
366
+ '979' => fixed(2), # International Premium Rate Service (IPRS)
367
+
368
+ '990' => fixed(2), # Spare code
369
+ '991' => fixed(2), # Trial of a proposed new international telecommunication public correspondence service, shared code
370
+ '992' => fixed(2), # Tajikistan (Republic of)
371
+ '993' => fixed(2), # Turkmenistan
372
+ '994' => fixed(2), # Azerbaijani Republic
373
+ '995' => fixed(2), # Georgia
374
+ '996' => fixed(2), # Kyrgyz Republic
375
+ '997' => fixed(2), # Spare code
376
+ '998' => fixed(2), # Uzbekistan (Republic of)
377
+ '999' => fixed(2), # Reserved for possible future use within the Telecommunications for Disaster Relief (TDR) concept
378
+ }
379
+ }
380
+
381
+ # Splits the phone number into pieces according to the country codes above.
382
+ #
383
+ def self.split(phone_number)
384
+ splitter_or_number, country_code, ndc, local = split_internal(phone_number) do |splitter, cc, ndc_local|
385
+ [splitter, cc, splitter.split_ndc(ndc_local)].flatten
386
+ end
387
+ return splitter_or_number if local.nil?
388
+
389
+ [country_code, ndc, splitter_or_number.split_local(local)].flatten
390
+ end
391
+
392
+ # Formats a E164 number according to local customs.
393
+ #
394
+ def self.formatted(phone_number, options = {})
395
+ splitter_or_number, cc, ndc, local = split_internal(phone_number) do |splitter, cc, ndc_local|
396
+ [splitter, cc, splitter.split_ndc(ndc_local)].flatten
397
+ end
398
+ return splitter_or_number if local.nil?
399
+
400
+ space = options[:spaces] || ' '
401
+ formatted_cc_ndc(cc, ndc, options[:format], space) + splitter_or_number.locally_formatted(local, space)
402
+ end
403
+
404
+ # Normalizes
405
+ #
406
+ def self.normalize(phone_number)
407
+ phone_number = phone_number.dup
408
+ phone_number.gsub!(/\D*/, '').gsub!(/^0+/, '') # Remove zeros at the beginning and non-digit chars
409
+ remove_relative_zeros! phone_number
410
+ end
411
+
412
+ # Returns true if the number starts with 4 digits and ends with 6-12 characters.
413
+ #
414
+ def self.vanity_number?(number)
415
+ (number.gsub(' ', '') =~ /^\d{4}\w{6,12}$/) ? true : false
416
+ end
417
+
418
+ # Converts any character in the vanity_number to it's numeric representation.
419
+ # Does not check if the passed number is a valid vanity_number, simply does replacement.
420
+ #
421
+ def self.vanity_to_number(vanity_number)
422
+ Vanity.replace(vanity_number)
423
+ end
424
+
425
+ module Vanity
426
+
427
+ @@mapping = [
428
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
429
+ '2223334445556667777888999922233344455566677778889999'
430
+ ]
431
+ def self.mapping
432
+ @@mapping
433
+ end
434
+
435
+ # Replaces vanity characters of passed number with correct digits.
436
+ # Does not check for validity of vanity_number. Simply replaces all characters in the number
437
+ #
438
+ def self.replace number
439
+ number.tr *mapping
440
+ end
441
+
442
+ end
443
+
444
+
445
+ private
446
+
447
+ # Removes 0s from partially normalized numbers such as:
448
+ # 410443643533
449
+ #
450
+ # Example:
451
+ # 410443643533 -> 41443643533
452
+ def self.remove_relative_zeros!(phone_number, format = nil)
453
+ '%s%s' % split_cc(phone_number).collect! { |code| code.gsub(/^0+/, '') }
454
+ end
455
+
456
+ # Formats country code and national destination code.
457
+ #
458
+ def self.formatted_cc_ndc(cc, ndc, format, space = nil)
459
+ space ||= ' '
460
+ format, split_phone_number = case format
461
+ when nil, :international_absolute, :international, :+
462
+ [ndc.blank? ? '+%s%s' : '+%s%s%s%s', [cc, space, ndc, space]]
463
+ when :international_relative
464
+ [ndc.blank? ? '00%s%s' : '00%s%s%s%s', [cc, space, ndc, space]]
465
+ when :national
466
+ [ndc.blank? ? '' : '0%s%s', [ndc, space]]
467
+ when :local
468
+ ['', []]
469
+ end
470
+ format % split_phone_number
471
+ end
472
+
473
+ def self.split_cc(phone_number)
474
+ split_internal(phone_number) do |splitter, cc, ndc_local|
475
+ [cc, ndc_local]
476
+ end
477
+ end
478
+
479
+ def self.split_cc_ndc(phone_number)
480
+ split_internal(phone_number) do |splitter, cc, ndc_local|
481
+ splitter ? [cc, splitter.split_ndc(ndc_local)].flatten : [cc, ndc_local, '']
482
+ end
483
+ end
484
+
485
+ def self.split_internal(phone_number)
486
+ number = phone_number.dup
487
+ presumed_code = ''
488
+ 1.upto(3) do |i|
489
+ presumed_code << number.slice!(0..0)
490
+ splitter = @@country_codes[i][presumed_code]
491
+ return yield(splitter, presumed_code, number) if splitter
492
+ end
493
+ return yield(nil, '', '')
494
+ end
495
+
496
+ end