i18n_phone_numbers 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ module I18nPhoneNumbers
2
+
3
+ module CountryCodeSource
4
+
5
+ FROM_NUMBER_WITH_PLUS_SIGN = 1
6
+ FROM_NUMBER_WITH_IDD = 5
7
+ FROM_NUMBER_WITHOUT_PLUS_SIGN = 10
8
+ FROM_DEFAULT_COUNTRY = 20
9
+
10
+ end
11
+
12
+ module PhoneNumberType
13
+
14
+ FIXED_LINE = 0
15
+ MOBILE = 1
16
+ # In some regions (e.g. the USA), it is impossible to distinguish between
17
+ # fixed-line and mobile numbers by looking at the phone number itself.
18
+ FIXED_LINE_OR_MOBILE = 2
19
+ # Freephone lines
20
+ TOLL_FREE = 3
21
+ PREMIUM_RATE = 4
22
+ # The cost of this call is shared between the caller and the recipient, and
23
+ # is hence typically less than PREMIUM_RATE calls. See
24
+ # http://en.wikipedia.org/wiki/Shared_Cost_Service for more information.
25
+ SHARED_COST = 5
26
+ # Voice over IP numbers. This includes TSoIP (Telephony Service over IP).
27
+ VOIP = 6
28
+ # A personal number is associated with a particular person, and may be routed
29
+ # to either a MOBILE or FIXED_LINE number. Some more information can be found
30
+ # here: http://en.wikipedia.org/wiki/Personal_Numbers
31
+ PERSONAL_NUMBER = 7
32
+ PAGER = 8
33
+ # Used for "Universal Access Numbers" or "Company Numbers". They may be
34
+ # further routed to specific offices, but allow one number to be used for a
35
+ # company.
36
+ UAN = 9
37
+ # Used for 'Voice Mail Access Numbers'.
38
+ VOICEMAIL = 10
39
+ # A phone number is of type UNKNOWN when it does not fit any of the known
40
+ # patterns for a specific country.
41
+ UNKNOWN = -1
42
+
43
+ end
44
+
45
+ # INTERNATIONAL and NATIONAL formats are consistent with the definition in
46
+ # ITU-T Recommendation E. 123. For example, the number of the Google Zurich
47
+ # office will be written as "+41 44 668 1800" in INTERNATIONAL format, and as
48
+ # "044 668 1800" in NATIONAL format. E164 format is as per INTERNATIONAL format
49
+ # but with no formatting applied, e.g. +41446681800.
50
+ module PhoneNumberFormat
51
+
52
+ E164 = 0
53
+ INTERNATIONAL = 1
54
+ NATIONAL = 2
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,345 @@
1
+ module I18nPhoneNumbers
2
+
3
+ class Metadata
4
+
5
+ # The ISO 3166-1 alpha-2 representation of a country/region
6
+ attr_accessor :id,
7
+
8
+ # The country calling code that one would dial from overseas when trying to
9
+ # dial a phone number in this country. For example, this would be "64" for
10
+ # New Zealand.
11
+ :country_code,
12
+
13
+ # Note that the number format here is used for formatting only, not parsing.
14
+ # Hence all the varied ways a user *may* write a number need not be recorded
15
+ # - just the ideal way we would like to format it for them. When this element
16
+ # is absent, the national significant number will be formatted as a whole
17
+ # without any formatting applied.
18
+ :number_formats,
19
+
20
+ # This field is populated only when the national significant number is
21
+ # formatted differently when it forms part of the INTERNATIONAL format
22
+ # and NATIONAL format. A case in point is mobile numbers in Argentina:
23
+ # The number, which would be written in INTERNATIONAL format as
24
+ # +54 9 343 555 1212, will be written as 0343 15 555 1212 for NATIONAL
25
+ # format. In this case, the prefix 9 is inserted when dialling from
26
+ # overseas, but otherwise the prefix 0 and the carrier selection code
27
+ # 15 (inserted after the area code of 343) is used.
28
+ :intl_number_formats,
29
+
30
+ # The general_desc contains information which is a superset of descriptions
31
+ # for all types of phone numbers. If any element is missing in the
32
+ # description of a specific type in the XML file, the element will inherit
33
+ # from its counterpart in the general_desc. Every locale is assumed to have
34
+ # fixed line and mobile numbers - if these types are missing in the XML
35
+ # file, they will inherit all fields from the general_desc. For all other
36
+ # types, if the whole type is missing in the xml file, it will be given a
37
+ # national_number_pattern of "NA" and a possible_number_pattern of "NA".
38
+ :general_desc,
39
+ :fixed_line,
40
+ :mobile,
41
+
42
+ # The international_prefix of country A is the number that needs to be
43
+ # dialled from country A to another country (country B). This is followed
44
+ # by the country code for country B. Note that some countries may have more
45
+ # than one international prefix, and for those cases, a regular expression
46
+ # matching the international prefixes will be stored in this field.
47
+ :international_prefix,
48
+
49
+ # If more than one international prefix is present, a preferred prefix can
50
+ # be specified here for out-of-country formatting purposes. If this field is
51
+ # not present, and multiple international prefixes are present, then "+"
52
+ # will be used instead.
53
+ :preferred_international_prefix,
54
+
55
+ # This field is populated only for countries or regions that share a country
56
+ # calling code. If a number matches this pattern, it could belong to this
57
+ # region. This is not intended as a replacement for IsValidForRegion, and
58
+ # does not mean the number must come from this region (for example, 800
59
+ # numbers are valid for all NANPA countries.) This field should be a regular
60
+ # expression of the expected prefix match.
61
+ :leading_digits,
62
+
63
+ # The leading zero in a phone number is meaningful in some countries (e.g.
64
+ # Italy). This means they cannot be dropped from the national number when
65
+ # converting into international format. If leading zeros are possible for
66
+ # valid international numbers for this region/country then set this to true.
67
+ # This only needs to be set for the region that is the main_country_for_code
68
+ # and all regions associated with that calling code will use the same
69
+ # setting.
70
+ :leading_zero_possible,
71
+
72
+ # The national prefix of country A is the number that needs to be dialled
73
+ # before the national significant number when dialling internally. This
74
+ # would not be dialled when dialling internationally. For example, in New
75
+ # Zealand, the number that would be locally dialled as 09 345 3456 would be
76
+ # dialled from overseas as +64 9 345 3456. In this case, 0 is the national
77
+ # prefix.
78
+ :national_prefix,
79
+
80
+ # This field is used for cases where the national prefix of a country
81
+ # contains a carrier selection code, and is written in the form of a
82
+ # regular expression. For example, to dial the number 2222-2222 in
83
+ # Fortaleza, Brazil (area code 85) using the long distance carrier Oi
84
+ # (selection code 31), one would dial 0 31 85 2222 2222. Assuming the
85
+ # only other possible carrier selection code is 32, the field will
86
+ # contain "03[12]".
87
+ #
88
+ # When it is missing from the XML file, this field inherits the value of
89
+ # national_prefix, if that is present.
90
+ :national_prefix_for_parsing,
91
+
92
+ # Default value to be used in any numberFormat missing this attribute
93
+ :national_prefix_formatting_rule,
94
+
95
+ # This field is only populated and used under very rare situations.
96
+ # For example, mobile numbers in Argentina are written in two completely
97
+ # different ways when dialed in-country and out-of-country
98
+ # (e.g. 0343 15 555 1212 is exactly the same number as +54 9 343 555 1212).
99
+ # This field is used together with national_prefix_for_parsing to transform
100
+ # the number into a particular representation for storing in the phonenumber
101
+ # proto buffer in those rare cases.
102
+ :national_prefix_transform_rule,
103
+
104
+ # Specifies whether the mobile and fixed-line patterns are the same or not.
105
+ # This is used to speed up determining phone number type in countries where
106
+ # these two types of phone numbers can never be distinguished.
107
+ :same_mobile_and_fixed_line_pattern, # true / false
108
+
109
+ :main_country_for_code
110
+
111
+
112
+ module ClassMethods
113
+
114
+ def load_from_xml(filename)
115
+
116
+ file = File.open(File.join(File.dirname(__FILE__), "data", filename), "rb")
117
+ contents = file.read
118
+ Hash.from_xml(contents)
119
+
120
+ end
121
+
122
+ # hash indexed by country alpha2 code (ex: "FR", "CH", "MA", a.s.o)
123
+ def territories
124
+
125
+ # territories are cached
126
+ return @territories if @territories
127
+
128
+
129
+ # if no yaml file to quickly load territories from,
130
+ # generate @territories from xml files
131
+ if !quick_load_territories
132
+
133
+ @territories = {}
134
+
135
+ files = [
136
+ "PhoneNumberMetaData.xml"
137
+ ]
138
+
139
+ files.each { |filename|
140
+
141
+ data = load_from_xml(filename)
142
+
143
+ raw_territories = data["phoneNumberMetadata"]["territories"]["territory"]
144
+ raw_territories = [raw_territories] if !(Array === raw_territories) # if only 1 territory in xml file
145
+
146
+ raw_territories.each { |territory|
147
+ @territories[territory["id"]] = self.new(territory)
148
+ }
149
+
150
+ }
151
+
152
+ end
153
+
154
+ @territories
155
+
156
+ end
157
+
158
+ def countryToMetadata
159
+
160
+ territories
161
+
162
+ end
163
+
164
+ def countryCodeToRegionCodeMap
165
+
166
+ return @countryCodeToRegionCodeMap if @countryCodeToRegionCodeMap
167
+
168
+ @countryCodeToRegionCodeMap = {}
169
+
170
+ territories.each do |id, t|
171
+
172
+ if !t.country_code.blank?
173
+
174
+ # main country should be first in the list
175
+ if t.main_country_for_code
176
+ (@countryCodeToRegionCodeMap[t.country_code] ||= []).unshift(id)
177
+ else
178
+ (@countryCodeToRegionCodeMap[t.country_code] ||= []) << id
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
185
+ @countryCodeToRegionCodeMap
186
+ end
187
+
188
+
189
+ # code to generate a YAML dump of @territories,
190
+ # generated file is used to quickly load territories next time
191
+ def dump_territories
192
+
193
+ # .yml file location
194
+ file_path = File.join(File.dirname(__FILE__), "data", "quick_load.yml")
195
+
196
+ # remove previous dump if exists
197
+ File.delete(file_path) if File.exists?(file_path)
198
+
199
+ # generate territories from xml files
200
+ t = territories
201
+
202
+ # quick fix multiline regexp strings that are not correctly dumped in yaml
203
+ t.each { |id, territory|
204
+ territory.national_prefix_for_parsing = territory.national_prefix_for_parsing.blank? ? territory.national_prefix_for_parsing :
205
+ territory.national_prefix_for_parsing.gsub(/\r?\n */, '')
206
+ }
207
+
208
+ # and dump into .yml file !
209
+
210
+ # Following code equivalent to :
211
+ #
212
+ # destFile = File.new(destPath,"w")
213
+ # destFile.print sql
214
+ #
215
+ # But when switching to ruby 1.9, we will be able to force output encoding to UTF-8 :
216
+ # (see : http://blog.grayproductions.net/articles/ruby_19s_three_default_encodings )
217
+ #
218
+ # File.open(destPath, "w:UTF-8") do |file| ...
219
+ #
220
+ File.open(file_path, "w") do |file|
221
+ file << "# ClicRDV comment :\n"
222
+ file << "#\n"
223
+ file << "# This is an auto-generated file, DON'T MODIFY IT MANUALLY !\n"
224
+ file << "#\n"
225
+ file << "# Call I18nPhoneNumbers::Metadata.dump_territories to generate\n"
226
+ file << "# it from xml files instead.\n"
227
+ file << "#\n"
228
+ file << YAML.dump(t)
229
+ end
230
+
231
+ end
232
+
233
+ # helper to quickly load territories from YAML dump instead of parsing XML files
234
+ def quick_load_territories
235
+
236
+ if File.exists?(File.join(File.dirname(__FILE__), "data", "quick_load.yml"))
237
+
238
+ @territories = File.open( File.join(File.dirname(__FILE__), "data", "quick_load.yml") ) { |infile|
239
+ YAML::load( infile )
240
+ }
241
+
242
+ return true
243
+ end
244
+
245
+ return false
246
+
247
+ end
248
+
249
+ end
250
+
251
+
252
+ module InstanceMethods
253
+
254
+ def initialize(hash = {})
255
+
256
+ self.country_code = hash["countryCode"].to_i
257
+ self.id = hash["id"]
258
+ self.main_country_for_code = hash["mainCountryForCode"] == "true"
259
+ self.international_prefix = hash["internationalPrefix"]
260
+ self.leading_digits = hash["leadingDigits"]
261
+ self.leading_zero_possible = hash["leadingZeroPossible"] == "true"
262
+ self.national_prefix = hash["nationalPrefix"] || ''
263
+ self.national_prefix_for_parsing = hash["nationalPrefixForParsing"] || self.national_prefix
264
+ self.national_prefix_formatting_rule = (hash["nationalPrefixFormattingRule"] || '').
265
+ sub("$NP", self.national_prefix).
266
+ sub("$FG", "\\\\1")
267
+
268
+ self.national_prefix_transform_rule = hash["nationalPrefixTransformRule"].blank? ? nil :
269
+ hash["nationalPrefixTransformRule"].gsub("$", '\\') # regexp substitution in ruby uses '\1' references instead of '$1'
270
+
271
+ self.preferred_international_prefix = hash["preferredInternationalPrefix"]
272
+
273
+
274
+ self.general_desc = Hash === hash["generalDesc"] ? I18nPhoneNumbers::PhoneNumberDesc.new(hash["generalDesc"]) : nil
275
+
276
+ self.fixed_line = Hash === hash["fixedLine"] ? I18nPhoneNumbers::PhoneNumberDesc.new(hash["fixedLine"]) : nil
277
+ if !self.fixed_line.nil? && self.fixed_line.possible_number_pattern.blank? && !self.general_desc.nil?
278
+ self.fixed_line.possible_number_pattern = self.general_desc.possible_number_pattern
279
+ end
280
+
281
+ self.mobile = Hash === hash["mobile"] ? I18nPhoneNumbers::PhoneNumberDesc.new(hash["mobile"]) : nil
282
+ if !self.mobile.nil? && self.mobile.possible_number_pattern.blank? && !self.general_desc.nil?
283
+ self.mobile.possible_number_pattern = self.general_desc.possible_number_pattern
284
+ end
285
+
286
+ if !self.mobile.nil? && !self.fixed_line.nil?
287
+ self.same_mobile_and_fixed_line_pattern = (self.mobile.national_number_pattern == self.fixed_line.national_number_pattern)
288
+ else
289
+ self.same_mobile_and_fixed_line_pattern = false # TODO : we don't know ?!?
290
+ end
291
+
292
+
293
+ # populate with available formats
294
+ self.number_formats = []
295
+ self.intl_number_formats = []
296
+
297
+ if Hash === hash["availableFormats"]
298
+
299
+ if !hash["availableFormats"]["numberFormat"].blank?
300
+
301
+ # we get either a string or and array of strings (damn you XML !)
302
+ numberFormatList = hash["availableFormats"]["numberFormat"].class == Array ?
303
+ hash["availableFormats"]["numberFormat"] :
304
+ [hash["availableFormats"]["numberFormat"]]
305
+
306
+ numberFormatList.each { |numberFormat|
307
+
308
+
309
+ # national format
310
+ nf = I18nPhoneNumbers::NumberFormat.new(numberFormat, self)
311
+
312
+ self.number_formats << nf
313
+
314
+
315
+ # international format
316
+ next if numberFormat["intlFormat"] == "NA" # skip national-only rule
317
+
318
+ numberFormat["format"] = numberFormat["intlFormat"] || numberFormat["format"]
319
+
320
+ nf = I18nPhoneNumbers::NumberFormat.new(numberFormat, self)
321
+
322
+ self.intl_number_formats << nf
323
+
324
+ }
325
+
326
+ end
327
+
328
+ end
329
+
330
+ end
331
+
332
+ def national_prefix_for_parsing
333
+
334
+ @national_prefix_for_parsing.nil? ? self.national_prefix : @national_prefix_for_parsing
335
+
336
+ end
337
+
338
+ end
339
+
340
+ extend ClassMethods
341
+ include InstanceMethods
342
+
343
+ end
344
+
345
+ end
@@ -0,0 +1,87 @@
1
+ module I18nPhoneNumbers
2
+
3
+ class NumberFormat
4
+
5
+ # pattern is a regex that is used to match the national (significant)
6
+ # number. For example, the pattern "(20)(\d{4})(\d{4})" will match number
7
+ # "2070313000", which is the national (significant) number for Google London.
8
+ # Note the presence of the parentheses, which are capturing groups what
9
+ # specifies the grouping of numbers.
10
+ attr_accessor :pattern,
11
+
12
+ # format specifies how the national (significant) number matched by
13
+ # pattern should be formatted.
14
+ # Using the same example as above, format could contain "$1 $2 $3",
15
+ # meaning that the number should be formatted as "20 7031 3000".
16
+ # Each $x are replaced by the numbers captured by group x in the
17
+ # regex specified by pattern.
18
+ :format,
19
+
20
+ # This field is a regex that is used to match a certain number of digits
21
+ # at the beginning of the national (significant) number. When the match is
22
+ # successful, the accompanying pattern and format should be used to format
23
+ # this number. For example, if leading_digits="[1-3]|44", then all the
24
+ # national numbers starting with 1, 2, 3 or 44 should be formatted using the
25
+ # accompanying pattern and format.
26
+ #
27
+ # The first leadingDigitsPattern matches up to the first three digits of the
28
+ # national (significant) number; the next one matches the first four digits,
29
+ # then the first five and so on, until the leadingDigitsPattern can uniquely
30
+ # identify one pattern and format to be used to format the number.
31
+ #
32
+ # In the case when only one formatting pattern exists, no
33
+ # leading_digits_pattern is needed.
34
+ :leading_digits_patterns,
35
+
36
+ # This field specifies how the national prefix ($NP) together with the first
37
+ # group ($FG) in the national significant number should be formatted in
38
+ # the NATIONAL format when a national prefix exists for a certain country.
39
+ # For example, when this field contains "($NP$FG)", a number from Beijing,
40
+ # China (whose $NP = 0), which would by default be formatted without
41
+ # national prefix as 10 1234 5678 in NATIONAL format, will instead be
42
+ # formatted as (010) 1234 5678; to format it as (0)10 1234 5678, the field
43
+ # would contain "($NP)$FG". Note $FG should always be present in this field,
44
+ # but $NP can be omitted. For example, having "$FG" could indicate the
45
+ # number should be formatted in NATIONAL format without the national prefix.
46
+ # This is commonly used to override the rule from generalDesc.
47
+ #
48
+ # When this field is missing, a number will be formatted without national
49
+ # prefix in NATIONAL format. This field does not affect how a number
50
+ # is formatted in other formats, such as INTERNATIONAL.
51
+ :national_prefix_formatting_rule,
52
+
53
+ # This field specifies how any carrier code ($CC) together with the first
54
+ # group ($FG) in the national significant number should be formatted
55
+ # when formatWithCarrierCode is called, if carrier codes are used for a
56
+ # certain country.
57
+ :carrier_code_formatting_rule
58
+
59
+ def initialize(hash, regionMetadata)
60
+
61
+ self.pattern = hash["pattern"] || ''
62
+ self.format = (hash["format"] || '').gsub("$", '\\') # regexp substitution in ruby uses '\1' references instead of '$1'
63
+
64
+ if !hash["leadingDigits"].blank?
65
+ leadingDigits = hash["leadingDigits"].class != Array ? [hash["leadingDigits"]] : hash["leadingDigits"]
66
+ self.leading_digits_patterns = leadingDigits.collect { |ld| ld.gsub(/\r?\n */,'') }
67
+ else
68
+ self.leading_digits_patterns = []
69
+ end
70
+
71
+ # national_prefix_formatting_rule
72
+ self.national_prefix_formatting_rule = hash["nationalPrefixFormattingRule"] ||
73
+ regionMetadata.national_prefix_formatting_rule
74
+
75
+ if !self.national_prefix_formatting_rule.blank?
76
+ self.national_prefix_formatting_rule = self.national_prefix_formatting_rule.
77
+ sub("$NP", regionMetadata.national_prefix).
78
+ sub("$FG", "\\\\1")
79
+ end
80
+
81
+ self.carrier_code_formatting_rule = hash["carrierCodeFormattingRule"] || ''
82
+
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,41 @@
1
+ module I18nPhoneNumbers
2
+
3
+ class PhoneNumber
4
+
5
+ attr_accessor :raw_input,
6
+ :country_code,
7
+ :country_code_source,
8
+ :national_number,
9
+ :preferred_domestic_carrier_code,
10
+ :italian_leading_zero
11
+
12
+ def initialize(attrs = {})
13
+
14
+ attrs.each_pair { |key, value|
15
+
16
+ if [:raw_input, :country_code, :country_code_source, :national_number,
17
+ :preferred_domestic_carrier_code, :italian_leading_zero].include?(key.to_sym)
18
+ self.send(key.to_s + "=", value)
19
+ end
20
+
21
+ }
22
+
23
+ end
24
+
25
+ # for tests purpose
26
+ def equals(other)
27
+
28
+ return false if !(self.class === other) # not the same class
29
+
30
+ [:country_code, :national_number, :italian_leading_zero].each do |attribute|
31
+ return false if self.send(attribute) != other.send(attribute)
32
+ end
33
+
34
+ return true
35
+
36
+ end
37
+
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,33 @@
1
+ module I18nPhoneNumbers
2
+
3
+ class PhoneNumberDesc
4
+
5
+ # The national_number_pattern is the pattern that a valid national
6
+ # significant number would match. This specifies information such as its
7
+ # total length and leading digits.
8
+ attr_accessor :national_number_pattern,
9
+
10
+ # The possible_number_pattern represents what a potentially valid phone
11
+ # number for this region may be written as. This is a superset of the
12
+ # national_number_pattern above and includes numbers that have the area code
13
+ # omitted. Typically the only restrictions here are in the number of digits.
14
+ # This could be used to highlight tokens in a text that may be a phone
15
+ # number, or to quickly prune numbers that could not possibly be a phone
16
+ # number for this locale.
17
+ :possible_number_pattern,
18
+
19
+ # An example national significant number for the specific type. It should
20
+ # not contain any formatting information.
21
+ :example_number
22
+
23
+ def initialize(hash)
24
+
25
+ self.national_number_pattern = (hash["nationalNumberPattern"] || '').gsub(/\r?\n */,'')
26
+ self.possible_number_pattern = (hash["possibleNumberPattern"] || '').gsub(/\r?\n */,'')
27
+ self.example_number = (hash["exampleNumber"] || '').gsub(/\r?\n */,'')
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end