globalphone 1.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.
- data/LICENSE +20 -0
- data/README.md +163 -0
- data/lib/global_phone.rb +10 -0
- data/lib/global_phone/context.rb +36 -0
- data/lib/global_phone/database.rb +48 -0
- data/lib/global_phone/format.rb +27 -0
- data/lib/global_phone/number.rb +91 -0
- data/lib/global_phone/parsing.rb +52 -0
- data/lib/global_phone/record.rb +26 -0
- data/lib/global_phone/region.rb +62 -0
- data/lib/global_phone/territory.rb +58 -0
- data/lib/global_phone/utils.rb +14 -0
- metadata +60 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Sam Stephenson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
# GlobalPhone
|
2
|
+
|
3
|
+
GlobalPhone parses, validates, and formats local and international phone numbers according to the [E.164 standard](http://en.wikipedia.org/wiki/E.164).
|
4
|
+
|
5
|
+
**Store and display phone numbers in your app.** Accept phone number input in national or international format. Convert phone numbers to international strings (`+13125551212`) for storage and retrieval. Present numbers in national format (`(312) 555-1212`) in your UI.
|
6
|
+
|
7
|
+
**Designed with the future in mind.** GlobalPhone uses format specifications from Google's open-source [libphonenumber](http://code.google.com/p/libphonenumber/) database. No need to upgrade the library when a new phone format is introduced—just generate a new copy of the database and check it into your app.
|
8
|
+
|
9
|
+
**Pure Ruby. No dependencies.** GlobalPhone is designed for Ruby 1.9.3 and up. (Works in 1.8.7, too—just bring your own `json` gem.)
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
1. Add the `global_phone` gem to your app. For example, using Bundler:
|
14
|
+
|
15
|
+
$ echo "gem 'global_phone'" >> Gemfile
|
16
|
+
$ bundle install
|
17
|
+
|
18
|
+
2. Use `global_phone_dbgen` to convert Google's libphonenumber `PhoneNumberMetaData.xml` file into a JSON database for GlobalPhone.
|
19
|
+
|
20
|
+
$ gem install global_phone_dbgen
|
21
|
+
$ global_phone_dbgen > db/global_phone.json
|
22
|
+
|
23
|
+
3. Tell GlobalPhone where to find the database. For example, in a Rails app, create an initializer in `config/initializers/global_phone.rb`:
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require 'global_phone'
|
27
|
+
GlobalPhone.db_path = Rails.root.join('db/global_phone.json')
|
28
|
+
```
|
29
|
+
|
30
|
+
## Examples
|
31
|
+
|
32
|
+
Parse an international number string into a `GlobalPhone::Number` object:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
number = GlobalPhone.parse('+1-312-555-1212')
|
36
|
+
# => #<GlobalPhone::Number territory=#<GlobalPhone::Territory country_code=1 name=US> national_string="3125551212">
|
37
|
+
```
|
38
|
+
|
39
|
+
Query the country code and likely territory name of the number:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
number.country_code
|
43
|
+
# => "1"
|
44
|
+
|
45
|
+
number.territory.name
|
46
|
+
# => "US"
|
47
|
+
```
|
48
|
+
|
49
|
+
Present the number in national and international formats:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
number.national_format
|
53
|
+
# => "(312) 555-1212"
|
54
|
+
|
55
|
+
number.international_format
|
56
|
+
# => "+1 312-555-1212"
|
57
|
+
```
|
58
|
+
|
59
|
+
Is the number valid? (Note: this is not definitive. For example, the number here is "valid" by format, but there are no US numbers that start with 555. The `valid?` method may return false positives, but *should not* return false negatives unless the database is out of date.)
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
number.valid?
|
63
|
+
# => true
|
64
|
+
```
|
65
|
+
|
66
|
+
Get the number's normalized E.164 international string:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
number.international_string
|
70
|
+
# => "+13125551212"
|
71
|
+
```
|
72
|
+
|
73
|
+
Parse a number in national format for a given territory:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
number = GlobalPhone.parse("(0) 20-7031-3000", :gb)
|
77
|
+
# => #<GlobalPhone::Number territory=#<GlobalPhone::Territory country_code=44 name=GB> national_string="2070313000">
|
78
|
+
```
|
79
|
+
|
80
|
+
Parse an international number using a territory's international dialing prefix:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
number = GlobalPhone.parse("00 1 3125551212", :gb)
|
84
|
+
# => #<GlobalPhone::Number territory=#<GlobalPhone::Territory country_code=1 name=US> national_string="3125551212">
|
85
|
+
```
|
86
|
+
|
87
|
+
Set the default territory to Great Britain (territory names are [ISO 3166-1 Alpha-2](http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) codes):
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
GlobalPhone.default_territory_name = :gb
|
91
|
+
# => :gb
|
92
|
+
|
93
|
+
GlobalPhone.parse("(0) 20-7031-3000")
|
94
|
+
# => #<GlobalPhone::Number territory=#<GlobalPhone::Territory country_code=44 name=GB> national_string="2070313000">
|
95
|
+
```
|
96
|
+
|
97
|
+
Shortcuts for validating a phone number:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
GlobalPhone.validate("+1 312-555-1212")
|
101
|
+
# => true
|
102
|
+
|
103
|
+
GlobalPhone.validate("+442070313000")
|
104
|
+
# => true
|
105
|
+
|
106
|
+
GlobalPhone.validate("(0) 20-7031-3000")
|
107
|
+
# => false
|
108
|
+
|
109
|
+
GlobalPhone.validate("(0) 20-7031-3000", :gb)
|
110
|
+
# => true
|
111
|
+
```
|
112
|
+
|
113
|
+
Shortcuts for normalizing a phone number in E.164 format:
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
GlobalPhone.normalize("(312) 555-1212")
|
117
|
+
# => "+13125551212"
|
118
|
+
|
119
|
+
GlobalPhone.normalize("+442070313000")
|
120
|
+
# => "+442070313000"
|
121
|
+
|
122
|
+
GlobalPhone.normalize("(0) 20-7031-3000")
|
123
|
+
# => nil
|
124
|
+
|
125
|
+
GlobalPhone.normalize("(0) 20-7031-3000", :gb)
|
126
|
+
# => "+442070313000"
|
127
|
+
```
|
128
|
+
|
129
|
+
## Caveats
|
130
|
+
|
131
|
+
GlobalPhone currently does not parse emergency numbers or SMS short code numbers.
|
132
|
+
|
133
|
+
Validation is not definitive and may return false positives, but *should not* return false negatives unless the database is out of date.
|
134
|
+
|
135
|
+
Territory heuristics are imprecise. Parsing a number will usually result in the territory being set to the primary territory of the region. For example, Canadian numbers will be parsed with a territory of `US`. (In most cases this does not matter, but if your application needs to perform geolocation using phone numbers, GlobalPhone may not be a good fit.)
|
136
|
+
|
137
|
+
## Development
|
138
|
+
|
139
|
+
The GlobalPhone source code is [hosted on GitHub](https://github.com/sstephenson/global_phone). You can check out a copy of the latest code using Git:
|
140
|
+
|
141
|
+
$ git clone https://github.com/sstephenson/global_phone.git
|
142
|
+
|
143
|
+
If you've found a bug or have a question, please open an issue on the [issue tracker](https://github.com/sstephenson/global_phone/issues). Or, clone the GlobalPhone repository, write a failing test case, fix the bug, and submit a pull request.
|
144
|
+
|
145
|
+
GlobalPhone is heavily inspired by Andreas Gal's [PhoneNumber.js](https://github.com/andreasgal/PhoneNumber.js) library.
|
146
|
+
|
147
|
+
### Version History
|
148
|
+
|
149
|
+
**1.0.1** (May 29, 2013)
|
150
|
+
|
151
|
+
* GlobalPhone::Number#to_s returns the E.164 international string.
|
152
|
+
* Ensure GlobalPhone::Number always returns strings for #national_format, #international_format, and #international_string, regardless of validity.
|
153
|
+
* Relax format restrictions to more loosely match available national number patterns.
|
154
|
+
|
155
|
+
**1.0.0** (May 28, 2013)
|
156
|
+
|
157
|
+
* Initial public release.
|
158
|
+
|
159
|
+
### License
|
160
|
+
|
161
|
+
Copyright © 2013 Sam Stephenson
|
162
|
+
|
163
|
+
Released under the MIT license. See [`LICENSE`](LICENSE) for details.
|
data/lib/global_phone.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'global_phone/database'
|
2
|
+
|
3
|
+
module GlobalPhone
|
4
|
+
module Context
|
5
|
+
attr_accessor :db_path
|
6
|
+
|
7
|
+
def db
|
8
|
+
@db ||= begin
|
9
|
+
raise NoDatabaseError, "set `db_path=' first" unless db_path
|
10
|
+
Database.load_file(db_path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_territory_name
|
15
|
+
@default_territory_name ||= :US
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_territory_name=(territory_name)
|
19
|
+
@default_territory_name = territory_name.to_s.intern
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse(string, territory_name = default_territory_name)
|
23
|
+
db.parse(string, territory_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def normalize(string, territory_name = default_territory_name)
|
27
|
+
number = parse(string, territory_name)
|
28
|
+
number.international_string if number
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(string, territory_name = default_territory_name)
|
32
|
+
number = parse(string, territory_name)
|
33
|
+
number && number.valid?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'global_phone/parsing'
|
2
|
+
require 'global_phone/region'
|
3
|
+
|
4
|
+
module GlobalPhone
|
5
|
+
class Database
|
6
|
+
include Parsing
|
7
|
+
|
8
|
+
def self.load_file(filename)
|
9
|
+
load(File.read(filename))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.load(json)
|
13
|
+
require 'json'
|
14
|
+
new(JSON.parse(json))
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :regions
|
18
|
+
|
19
|
+
def initialize(record_data)
|
20
|
+
@regions = record_data.map { |data| Region.new(data) }
|
21
|
+
@territories_by_name = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def region(country_code)
|
25
|
+
regions_by_country_code[country_code.to_s]
|
26
|
+
end
|
27
|
+
|
28
|
+
def territory(name)
|
29
|
+
name = name.to_s.upcase
|
30
|
+
@territories_by_name[name] ||= if region = region_for_territory(name)
|
31
|
+
region.territory(name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class.name}>"
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def regions_by_country_code
|
41
|
+
@regions_by_country_code ||= Hash[*regions.map { |r| [r.country_code, r] }.flatten]
|
42
|
+
end
|
43
|
+
|
44
|
+
def region_for_territory(name)
|
45
|
+
regions.find { |r| r.has_territory?(name) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'global_phone/record'
|
2
|
+
|
3
|
+
module GlobalPhone
|
4
|
+
class Format < Record
|
5
|
+
field 0, :pattern do |p| /^#{p}$/ end
|
6
|
+
field 1, :national_format_rule
|
7
|
+
field 2, :leading_digits do |d| /^#{d}/ end
|
8
|
+
field 3, :national_prefix_formatting_rule
|
9
|
+
field 4, :international_format_rule, :fallback => :national_format_rule
|
10
|
+
|
11
|
+
def match(national_string, match_leading_digits = true)
|
12
|
+
return false if match_leading_digits && leading_digits && national_string !~ leading_digits
|
13
|
+
national_string =~ pattern
|
14
|
+
end
|
15
|
+
|
16
|
+
def format_replacement_string(type)
|
17
|
+
format_rule = send(:"#{type}_format_rule")
|
18
|
+
format_rule.to_s.gsub("$", "\\") unless format_rule == "NA"
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(national_string, type)
|
22
|
+
if replacement = format_replacement_string(type)
|
23
|
+
national_string.gsub(pattern, replacement)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module GlobalPhone
|
4
|
+
class Number
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
E161_MAPPING = Hash[*"a2b2c2d3e3f3g4h4i4j5k5l5m6n6o6p7q7r7s7t8u8v8w9x9y9z9".split("")]
|
8
|
+
VALID_ALPHA_CHARS = /[a-zA-Z]/
|
9
|
+
LEADING_PLUS_CHARS = /^\++/
|
10
|
+
NON_DIALABLE_CHARS = /[^,#+\*\d]/
|
11
|
+
SPLIT_FIRST_GROUP = /^(\d+)(.*)$/
|
12
|
+
|
13
|
+
def self.normalize(string)
|
14
|
+
string.to_s.
|
15
|
+
gsub(VALID_ALPHA_CHARS) { |c| E161_MAPPING[c.downcase] }.
|
16
|
+
gsub(LEADING_PLUS_CHARS, "+").
|
17
|
+
gsub(NON_DIALABLE_CHARS, "")
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :territory, :national_string
|
21
|
+
|
22
|
+
def_delegator :territory, :region
|
23
|
+
def_delegator :territory, :country_code
|
24
|
+
def_delegator :territory, :national_prefix
|
25
|
+
def_delegator :territory, :national_pattern
|
26
|
+
|
27
|
+
def initialize(territory, national_string)
|
28
|
+
@territory = territory
|
29
|
+
@national_string = national_string
|
30
|
+
end
|
31
|
+
|
32
|
+
def national_format
|
33
|
+
@national_format ||= begin
|
34
|
+
if format && result = format.apply(national_string, :national)
|
35
|
+
apply_national_prefix_format(result)
|
36
|
+
else
|
37
|
+
national_string
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def international_string
|
43
|
+
@international_string ||= international_format.gsub(NON_DIALABLE_CHARS, "")
|
44
|
+
end
|
45
|
+
|
46
|
+
def international_format
|
47
|
+
@international_format ||= begin
|
48
|
+
if format && formatted_number = format.apply(national_string, :international)
|
49
|
+
"+#{country_code} #{formatted_number}"
|
50
|
+
else
|
51
|
+
"+#{country_code} #{national_string}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?
|
57
|
+
!!(format && national_string =~ national_pattern)
|
58
|
+
end
|
59
|
+
|
60
|
+
def inspect
|
61
|
+
"#<#{self.class.name} territory=#{territory.inspect} national_string=#{national_string.inspect}>"
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
international_string
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
def format
|
70
|
+
@format ||= find_format_for(national_string)
|
71
|
+
end
|
72
|
+
|
73
|
+
def find_format_for(string)
|
74
|
+
region.formats.detect { |format| format.match(string) } ||
|
75
|
+
region.formats.detect { |format| format.match(string, false) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def apply_national_prefix_format(result)
|
79
|
+
prefix = national_prefix_formatting_rule
|
80
|
+
return result unless prefix && match = result.match(SPLIT_FIRST_GROUP)
|
81
|
+
|
82
|
+
prefix = prefix.gsub("$NP", national_prefix)
|
83
|
+
prefix = prefix.gsub("$FG", match[1])
|
84
|
+
result = "#{prefix} #{match[2]}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def national_prefix_formatting_rule
|
88
|
+
format.national_prefix_formatting_rule || territory.national_prefix_formatting_rule
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'global_phone/number'
|
2
|
+
require 'global_phone/utils'
|
3
|
+
|
4
|
+
module GlobalPhone
|
5
|
+
module Parsing
|
6
|
+
def parse(string, territory_name)
|
7
|
+
string = Number.normalize(string)
|
8
|
+
territory = self.territory(territory_name)
|
9
|
+
raise ArgumentError, "unknown territory `#{territory_name}'" unless territory
|
10
|
+
|
11
|
+
if starts_with_plus?(string)
|
12
|
+
parse_international_string(string)
|
13
|
+
elsif string =~ territory.international_prefix
|
14
|
+
string = strip_international_prefix(territory, string)
|
15
|
+
parse_international_string(string)
|
16
|
+
else
|
17
|
+
territory.parse_national_string(string)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_international_string(string)
|
22
|
+
string = Number.normalize(string)
|
23
|
+
string = strip_leading_plus(string) if starts_with_plus?(string)
|
24
|
+
|
25
|
+
if region = region_for_string(string)
|
26
|
+
region.parse_national_string(string)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
def starts_with_plus?(string)
|
32
|
+
string[0, 1] == "+"
|
33
|
+
end
|
34
|
+
|
35
|
+
def strip_leading_plus(string)
|
36
|
+
string[1..-1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def strip_international_prefix(territory, string)
|
40
|
+
string.sub(territory.international_prefix, "")
|
41
|
+
end
|
42
|
+
|
43
|
+
def region_for_string(string)
|
44
|
+
candidates = country_code_candidates_for(string)
|
45
|
+
Utils.map_detect(candidates) { |country_code| region(country_code) }
|
46
|
+
end
|
47
|
+
|
48
|
+
def country_code_candidates_for(string)
|
49
|
+
(1..3).map { |length| string[0, length] }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module GlobalPhone
|
4
|
+
class Record
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def self.field(index, name, options = {}, &block)
|
8
|
+
if block
|
9
|
+
transform_method_name = :"transform_field_#{name}"
|
10
|
+
define_method(transform_method_name, block)
|
11
|
+
end
|
12
|
+
|
13
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
14
|
+
def #{name}
|
15
|
+
value = @data[#{index.inspect}]
|
16
|
+
#{"value = #{transform_method_name}(value) if value" if block}
|
17
|
+
value #{"|| #{options[:fallback]}" if options[:fallback]}
|
18
|
+
end
|
19
|
+
RUBY
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(data)
|
23
|
+
@data = data
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'global_phone/format'
|
2
|
+
require 'global_phone/record'
|
3
|
+
require 'global_phone/territory'
|
4
|
+
require 'global_phone/utils'
|
5
|
+
|
6
|
+
module GlobalPhone
|
7
|
+
class Region < Record
|
8
|
+
field 0, :country_code
|
9
|
+
field 1, :format_record_data
|
10
|
+
field 2, :territory_record_data
|
11
|
+
field 3, :international_prefix do |p| /^(?:#{p})/ end
|
12
|
+
field 4, :national_prefix
|
13
|
+
field 5, :national_prefix_for_parsing do |p| /^(?:#{p})/ end
|
14
|
+
field 6, :national_prefix_transform_rule
|
15
|
+
|
16
|
+
def formats
|
17
|
+
@formats ||= format_record_data.map { |data| Format.new(data) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def territories
|
21
|
+
@territories ||= territory_record_data.map { |data| Territory.new(data, self) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def territory(name)
|
25
|
+
name = name.to_s.upcase
|
26
|
+
territories.detect { |region| region.name == name }
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_territory?(name)
|
30
|
+
territory_names.include?(name.to_s.upcase)
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_national_string(string)
|
34
|
+
string = Number.normalize(string)
|
35
|
+
if starts_with_country_code?(string)
|
36
|
+
string = strip_country_code(string)
|
37
|
+
find_first_parsed_national_string_from_territories(string)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def inspect
|
42
|
+
"#<#{self.class.name} country_code=#{country_code} territories=[#{territory_names.join(",")}]>"
|
43
|
+
end
|
44
|
+
|
45
|
+
protected
|
46
|
+
def territory_names
|
47
|
+
territory_record_data.map(&:first)
|
48
|
+
end
|
49
|
+
|
50
|
+
def starts_with_country_code?(string)
|
51
|
+
string.index(country_code) == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
def strip_country_code(string)
|
55
|
+
string[country_code.length..-1]
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_first_parsed_national_string_from_territories(string)
|
59
|
+
Utils.map_detect(territories) { |territory| territory.parse_national_string(string) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'global_phone/number'
|
2
|
+
require 'global_phone/record'
|
3
|
+
|
4
|
+
module GlobalPhone
|
5
|
+
class Territory < Record
|
6
|
+
field 0, :name
|
7
|
+
field 1, :possible_pattern do |p| /^#{p}$/ end
|
8
|
+
field 2, :national_pattern do |p| /^#{p}$/ end
|
9
|
+
field 3, :national_prefix_formatting_rule
|
10
|
+
|
11
|
+
attr_reader :region
|
12
|
+
|
13
|
+
def_delegator :region, :country_code
|
14
|
+
def_delegator :region, :international_prefix
|
15
|
+
def_delegator :region, :national_prefix
|
16
|
+
def_delegator :region, :national_prefix_for_parsing
|
17
|
+
def_delegator :region, :national_prefix_transform_rule
|
18
|
+
|
19
|
+
def initialize(data, region)
|
20
|
+
super(data)
|
21
|
+
@region = region
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_national_string(string)
|
25
|
+
string = normalize(string)
|
26
|
+
Number.new(self, string) if possible?(string)
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<#{self.class.name} country_code=#{country_code} name=#{name}>"
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def strip_national_prefix(string)
|
35
|
+
if national_prefix_for_parsing
|
36
|
+
transform_rule = national_prefix_transform_rule || ""
|
37
|
+
transform_rule = transform_rule.gsub("$", "\\")
|
38
|
+
string_without_prefix = string.sub(national_prefix_for_parsing, transform_rule)
|
39
|
+
elsif starts_with_national_prefix?(string)
|
40
|
+
string_without_prefix = string[national_prefix.length..-1]
|
41
|
+
end
|
42
|
+
|
43
|
+
possible?(string_without_prefix) ? string_without_prefix : string
|
44
|
+
end
|
45
|
+
|
46
|
+
def normalize(string)
|
47
|
+
strip_national_prefix(Number.normalize(string))
|
48
|
+
end
|
49
|
+
|
50
|
+
def possible?(string)
|
51
|
+
string =~ possible_pattern
|
52
|
+
end
|
53
|
+
|
54
|
+
def starts_with_national_prefix?(string)
|
55
|
+
national_prefix && string.index(national_prefix) == 0
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
metadata
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: globalphone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sam Stephenson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-01 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: GlobalPhone parses, validates, and formats local and international phone
|
15
|
+
numbers according to the E.164 standard using the rules specified in Google's libphonenumber
|
16
|
+
database.
|
17
|
+
email:
|
18
|
+
- sstephenson@gmail.com
|
19
|
+
executables: []
|
20
|
+
extensions: []
|
21
|
+
extra_rdoc_files: []
|
22
|
+
files:
|
23
|
+
- README.md
|
24
|
+
- LICENSE
|
25
|
+
- lib/global_phone/context.rb
|
26
|
+
- lib/global_phone/database.rb
|
27
|
+
- lib/global_phone/format.rb
|
28
|
+
- lib/global_phone/number.rb
|
29
|
+
- lib/global_phone/parsing.rb
|
30
|
+
- lib/global_phone/record.rb
|
31
|
+
- lib/global_phone/region.rb
|
32
|
+
- lib/global_phone/territory.rb
|
33
|
+
- lib/global_phone/utils.rb
|
34
|
+
- lib/global_phone.rb
|
35
|
+
homepage: https://github.com/Lundalogik/globalphone_fork
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
requirements: []
|
55
|
+
rubyforge_project:
|
56
|
+
rubygems_version: 1.8.24
|
57
|
+
signing_key:
|
58
|
+
specification_version: 3
|
59
|
+
summary: Parse, validate, and format phone numbers using Google's libphonenumber database
|
60
|
+
test_files: []
|