line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Number::MuPhone; |
2
|
1
|
|
|
1
|
|
78076
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
34
|
|
3
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
28
|
|
4
|
1
|
|
|
1
|
|
14
|
use v5.020; |
|
1
|
|
|
|
|
3
|
|
5
|
1
|
|
|
1
|
|
783
|
use Moo; |
|
1
|
|
|
|
|
12846
|
|
|
1
|
|
|
|
|
6
|
|
6
|
1
|
|
|
1
|
|
2177
|
use Types::Standard qw( Maybe Str ); |
|
1
|
|
|
|
|
80892
|
|
|
1
|
|
|
|
|
9
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
$Number::MuPhone::VERSION = '1.0'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
our $MUPHONE_BASE_DIR = $ENV{MUPHONE_BASE_DIR} || $ENV{HOME}.'/.muphone'; |
11
|
|
|
|
|
|
|
our $EXTENSION_REGEX = qr/(?:\*|extension|ext|x)/; |
12
|
|
|
|
|
|
|
our $DIAL_PAUSE = ',,,'; |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# if custom data module exists, load it, else use distribution default |
15
|
|
|
|
|
|
|
# (which will most likely be out of date) |
16
|
|
|
|
|
|
|
our $MUPHONE_DATA; |
17
|
|
|
|
|
|
|
my $data_module_path = "$MUPHONE_BASE_DIR/lib/NumberMuPhoneData.pm"; |
18
|
|
|
|
|
|
|
if (-f $data_module_path) { |
19
|
|
|
|
|
|
|
require $data_module_path; |
20
|
|
|
|
|
|
|
} |
21
|
|
|
|
|
|
|
else { |
22
|
|
|
|
|
|
|
require Number::MuPhone::Data; |
23
|
|
|
|
|
|
|
} |
24
|
|
|
|
|
|
|
# Let's import the var shortcut to save typing |
25
|
|
|
|
|
|
|
Number::MuPhone::Data->import('$MUPHONE_DATA'); |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
################################################################################ |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 NAME |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
Number::MuPhone - parsing and using phone numbers in pure Perl |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
NOTE: this is a full rewrite and is not backwards compatible with earlier |
34
|
|
|
|
|
|
|
versions of this module. |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=head1 DESCRIPTION |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
Parse, validate (loosely in some cases) and display phone numbers as expected. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
This has stripped down functionality compared to libphonenumber, but it is |
41
|
|
|
|
|
|
|
also Pure Perl (TM), is a bit simpler to use, and contains the core functionality |
42
|
|
|
|
|
|
|
needed by common use cases. |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
If you have functionality requests, please let me know: |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
All number regexes are derived from the XML file supplied by: |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
https://github.com/google/libphonenumber/ |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=head2 BASIC USAGE |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
Instantiate an instance using one of the following syntaxes |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
# single arg: E.123 formatted number, scalar shortcut |
56
|
|
|
|
|
|
|
my $num = Number::MuPhone->new('+1 203 503 1199'); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# single arg: E.123 formatted number, hashref format |
59
|
|
|
|
|
|
|
my $num = Number::MuPhone->new({ |
60
|
|
|
|
|
|
|
number => '+1 203 503 1199' |
61
|
|
|
|
|
|
|
}); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# double arg, number and country - number can be in local or E.123 format, scalar args |
64
|
|
|
|
|
|
|
my $num = Number::MuPhone->new('+1 203 503 1199','US"); |
65
|
|
|
|
|
|
|
my $num = Number::MuPhone->new('(203) 503-1199','US'); |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
# double arg, number and country - number can be in local or E.123 format, hashref args |
68
|
|
|
|
|
|
|
my $num = Number::MuPhone->new({ |
69
|
|
|
|
|
|
|
number => '+1 203 503 1199' |
70
|
|
|
|
|
|
|
country => 'US', |
71
|
|
|
|
|
|
|
}); |
72
|
|
|
|
|
|
|
my $num = Number::MuPhone->new({ |
73
|
|
|
|
|
|
|
number => '(203) 503-1199' |
74
|
|
|
|
|
|
|
country => 'US', |
75
|
|
|
|
|
|
|
}); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
# after instantiation, check all is well before using the object |
78
|
|
|
|
|
|
|
if ($num->error) { |
79
|
|
|
|
|
|
|
# process the error |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
=head2 KEEPING UP TO DATE WITH CHANGES... |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
The data used to validate and format the phone numbers comes fropm Google's libphonenumber: |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
TODO: add URL |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
This distribution comes with a reasonably recent copy of the libphonenumber source XML, but |
89
|
|
|
|
|
|
|
you can also set up a cron to update your source data weekly, to ensure you don't have |
90
|
|
|
|
|
|
|
problems with new area codes as they get added (this happens probably more often than you think). |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
By default, MuPhone's update script (perl-muphone-build-data) will create a ~/.muphon |
93
|
|
|
|
|
|
|
directory and dump everything in there if you choose to update periodically (or when |
94
|
|
|
|
|
|
|
starting a Docker container, say) |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
If you want to store the data elsewhere, set the MUPHONE_BASE_DIR env var to specify |
97
|
|
|
|
|
|
|
where you want it stored. Wherever you store it, the directory must be writeable by |
98
|
|
|
|
|
|
|
the user. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Currently, the extractor script only grabs the data we need, and removes spacing, to keep the size down. |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
If you want to examine all available data, set $DEBUG=1 (add in padding) and set |
103
|
|
|
|
|
|
|
$STRIP_SUPERFLUOUS_DATA=0 in the script and run it again. |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
for the following, paths are relative to the ~/.muphone or $ENV{MUPHONE_BASE_DIR} dirs as appropriate |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
./etc/PhoneNumberMetadata.xml - the libphonenumber source XML file |
108
|
|
|
|
|
|
|
./lib/NumberMuPhoneData.pm - the generated Number::MuPhone::Data |
109
|
|
|
|
|
|
|
./t/check_data_module.t - a little sanity script that runs after creating the data file |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
=head3 Initial run |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
Optionally, set the MUPHONE_BASE_DIR environment variable to point to your config directory (must be writeable). |
114
|
|
|
|
|
|
|
Otherwise, ~/.muphone will get used (default). |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
As the user, run: |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
perl-muphone-build-data |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Confirm the tests pass and the files are created (if not error output, tests passed). |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head3 Set up the cron to run weekly to update the data |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# using default data dir |
125
|
|
|
|
|
|
|
0 5 * * 1 /usr/local/bin/perl-muphone-build-data |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
# using user specific data dir |
128
|
|
|
|
|
|
|
0 5 * * 1 MUPHONE_BASE_DIR=/path/to/config /usr/local/bin/perl-muphone-build-data |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
=head1 PUBLIC ATTRIBUTES |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=cut |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
around BUILDARGS => sub { |
136
|
|
|
|
|
|
|
my ( $orig, $class, @args ) = @_; |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
# args are probably a hashref - { number => $number, country => 'US' } |
139
|
|
|
|
|
|
|
# but can use a shortcut, if preferred |
140
|
|
|
|
|
|
|
# ($number, 'US') |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
if (ref $args[0] ne 'HASH' and @args>2) { |
143
|
|
|
|
|
|
|
die "Bad args - must be a hashref of name args or (\$num,\$country_code)"; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
if (!ref $args[0]) { |
147
|
|
|
|
|
|
|
$args[0] = { number => $args[0] }; |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$args[0]->{country} = pop @args |
150
|
|
|
|
|
|
|
if $args[1]; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
return $class->$orig(@args); |
154
|
|
|
|
|
|
|
}; |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
sub BUILD { |
157
|
17
|
|
|
17
|
0
|
1730
|
my ($self,$arg) = @_; |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# extract number and extension, determine countrycode from number, |
160
|
|
|
|
|
|
|
# strip off possible national/international dial prefix |
161
|
|
|
|
|
|
|
# and store attributes as needed |
162
|
17
|
|
|
|
|
37
|
$self->_process_raw_number; |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
} |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head2 number |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
The raw number sent in at instantiation - not needed (outside of logging, maybe) |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=cut |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
has number => ( |
173
|
|
|
|
|
|
|
isa => Str, |
174
|
|
|
|
|
|
|
is => 'ro', |
175
|
|
|
|
|
|
|
required => 1, |
176
|
|
|
|
|
|
|
); |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
=head2 extension |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
Extenstion number (digits only) |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
=cut |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
has extension => ( |
185
|
|
|
|
|
|
|
is => 'rw', |
186
|
|
|
|
|
|
|
default => '' |
187
|
|
|
|
|
|
|
); |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=head2 country |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
The 2 character country code sent in instantiation, or inferred from an E.123 number |
192
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=cut |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
# 2 char country code - either explicitly sent, to inferred from the number / config |
196
|
|
|
|
|
|
|
has country => ( |
197
|
|
|
|
|
|
|
isa => Maybe[Str], |
198
|
|
|
|
|
|
|
is => 'rw', |
199
|
|
|
|
|
|
|
lazy => 1, |
200
|
|
|
|
|
|
|
); |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
=head2 error |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
If the args don't point to a valid number at instantiation, this error will be set |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
=cut |
207
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
has error => ( |
209
|
|
|
|
|
|
|
isa => Str, |
210
|
|
|
|
|
|
|
is => 'rw', |
211
|
|
|
|
|
|
|
default => '', |
212
|
|
|
|
|
|
|
); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head2 country_name |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
Full text name of country() |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=cut |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
has country_name => ( |
221
|
|
|
|
|
|
|
is => 'lazy', |
222
|
|
|
|
|
|
|
); |
223
|
|
|
|
|
|
|
sub _build_country_name { |
224
|
3
|
|
|
3
|
|
4396
|
my $self = shift; |
225
|
3
|
|
|
|
|
53
|
return $MUPHONE_DATA->{territories}->{ $self->country }->{TerritoryName}; |
226
|
|
|
|
|
|
|
} |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=head2 country_code |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
1-3 digit country code |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
=cut |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
has country_code => ( |
235
|
|
|
|
|
|
|
is => 'lazy', |
236
|
|
|
|
|
|
|
); |
237
|
|
|
|
|
|
|
sub _build_country_code { |
238
|
6
|
|
|
6
|
|
1193
|
my $self = shift; |
239
|
6
|
|
|
|
|
106
|
return $MUPHONE_DATA->{territories}->{ $self->country }->{countryCode}; |
240
|
|
|
|
|
|
|
} |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=head2 national_dial |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
How you would dial this number within the country (including national dial code) |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=cut |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
has national_dial => ( |
249
|
|
|
|
|
|
|
is => 'lazy', |
250
|
|
|
|
|
|
|
); |
251
|
|
|
|
|
|
|
sub _build_national_dial { |
252
|
4
|
|
|
4
|
|
32
|
my $self = shift; |
253
|
4
|
100
|
|
|
|
44
|
my $dial_prefix = $self->_national_prefix_optional_when_formatting |
254
|
|
|
|
|
|
|
? '' |
255
|
|
|
|
|
|
|
: $self->_national_dial_prefix; |
256
|
|
|
|
|
|
|
|
257
|
4
|
|
|
|
|
82
|
return $dial_prefix.$self->_cleaned_number.$self->_extension_dial; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
=head2 national_display |
261
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
Display this number in the national number format |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
=cut |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# How do you display the number when you're in the country? |
267
|
|
|
|
|
|
|
# this default should work for most countries |
268
|
|
|
|
|
|
|
has national_display => ( |
269
|
|
|
|
|
|
|
is => 'ro', |
270
|
|
|
|
|
|
|
lazy => 1, |
271
|
|
|
|
|
|
|
default => sub { |
272
|
|
|
|
|
|
|
my $self = shift; |
273
|
|
|
|
|
|
|
my $dial_prefix = $self->_national_prefix_optional_when_formatting |
274
|
|
|
|
|
|
|
? '' |
275
|
|
|
|
|
|
|
: $self->_national_dial_prefix; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
return $dial_prefix.$self->_formatted_number.$self->_extension_display; |
278
|
|
|
|
|
|
|
} |
279
|
|
|
|
|
|
|
); |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
=head2 national_display |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
Display this number in the international number format (E.123) |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
=cut |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
has international_display => ( |
288
|
|
|
|
|
|
|
is => 'ro', |
289
|
|
|
|
|
|
|
lazy => 1, |
290
|
|
|
|
|
|
|
default => sub { |
291
|
|
|
|
|
|
|
my $self = shift; |
292
|
|
|
|
|
|
|
return '+'.$self->country_code.' '.$self->_formatted_number.$self->_extension_display; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
); |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
=head2 e164 |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
The number in E.164 format (+$COUNTRY_CODE$NUMBER[;ext=$EXTENSION]) |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
=cut |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
has e164 => ( |
303
|
|
|
|
|
|
|
is => 'lazy', |
304
|
|
|
|
|
|
|
); |
305
|
|
|
|
|
|
|
sub _build_e164 { |
306
|
3
|
|
|
3
|
|
1756
|
my $self = shift; |
307
|
3
|
100
|
|
|
|
18
|
my $ext = $self->extension |
308
|
|
|
|
|
|
|
? ";ext=".$self->extension |
309
|
|
|
|
|
|
|
: ''; |
310
|
3
|
|
|
|
|
50
|
return $self->e164_no_ext.$ext; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
=head2 e164_no_ext |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
The number in E.164 format, but with no extension (+$COUNTRY_CODE$NUMBER) |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
=cut |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
has e164_no_ext => ( |
320
|
|
|
|
|
|
|
is => 'lazy', |
321
|
|
|
|
|
|
|
); |
322
|
|
|
|
|
|
|
sub _build_e164_no_ext { |
323
|
3
|
|
|
3
|
|
1828
|
my $self = shift; |
324
|
3
|
|
|
|
|
50
|
return '+'.$self->country_code.$self->_cleaned_number; |
325
|
|
|
|
|
|
|
} |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
# number with international and national dial codes, and all non digits removed |
328
|
|
|
|
|
|
|
has _cleaned_number => ( |
329
|
|
|
|
|
|
|
is => 'rw', |
330
|
|
|
|
|
|
|
default => '', |
331
|
|
|
|
|
|
|
); |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
# basic validation of a number via this regex |
334
|
|
|
|
|
|
|
has _national_number_regex => ( |
335
|
|
|
|
|
|
|
is => 'lazy', |
336
|
|
|
|
|
|
|
); |
337
|
|
|
|
|
|
|
sub _build__national_number_regex { |
338
|
14
|
|
|
14
|
|
107
|
my $self = shift; |
339
|
14
|
|
|
|
|
212
|
my $regex_string = $MUPHONE_DATA->{territories}->{ $self->country }->{generalDesc}->{nationalNumberPattern}; |
340
|
14
|
|
|
|
|
234
|
return qr/^$regex_string$/; |
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# Display number without international or nation dial prefixes |
344
|
|
|
|
|
|
|
# built by _process_raw_number |
345
|
|
|
|
|
|
|
has _formatted_number => ( |
346
|
|
|
|
|
|
|
is => 'rw', |
347
|
|
|
|
|
|
|
); |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# Boolean used to help determine how to display a number |
350
|
|
|
|
|
|
|
# built in sub _process_raw_number |
351
|
|
|
|
|
|
|
has _national_prefix_optional_when_formatting => ( |
352
|
|
|
|
|
|
|
is => 'rw', |
353
|
|
|
|
|
|
|
); |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
# add pause to extension to create dial |
356
|
|
|
|
|
|
|
has _extension_dial => ( |
357
|
|
|
|
|
|
|
is => 'lazy', |
358
|
|
|
|
|
|
|
); |
359
|
|
|
|
|
|
|
sub _build__extension_dial { |
360
|
4
|
|
|
4
|
|
31
|
my $self = shift; |
361
|
4
|
100
|
|
|
|
39
|
return $self->extension |
362
|
|
|
|
|
|
|
? $DIAL_PAUSE.$self->extension |
363
|
|
|
|
|
|
|
: ''; |
364
|
|
|
|
|
|
|
} |
365
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
# prefix you dial when dialing the _cleaned_number within the country |
367
|
|
|
|
|
|
|
has _national_dial_prefix => ( |
368
|
|
|
|
|
|
|
is => 'lazy', |
369
|
|
|
|
|
|
|
); |
370
|
|
|
|
|
|
|
sub _build__national_dial_prefix { |
371
|
8
|
|
|
8
|
|
2792
|
my $self = shift; |
372
|
8
|
|
|
|
|
129
|
$MUPHONE_DATA->{territories}->{ $self->country }->{nationalPrefix}; |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# how to display the extension text + number (currently only in English) |
376
|
|
|
|
|
|
|
has _extension_display => ( |
377
|
|
|
|
|
|
|
is => 'lazy', |
378
|
|
|
|
|
|
|
); |
379
|
|
|
|
|
|
|
sub _build__extension_display { |
380
|
10
|
|
|
10
|
|
1303
|
my $self = shift; |
381
|
10
|
100
|
|
|
|
76
|
my $ext = |
382
|
|
|
|
|
|
|
return $self->extension |
383
|
|
|
|
|
|
|
? ' '.$self->_extension_text.' '.$self->extension |
384
|
|
|
|
|
|
|
: ''; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
# text to display befor an extension |
388
|
|
|
|
|
|
|
has _extension_text => ( |
389
|
|
|
|
|
|
|
is => 'ro', |
390
|
|
|
|
|
|
|
default => 'ext', |
391
|
|
|
|
|
|
|
); |
392
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
# helper method to get the country for a number, country, or object |
394
|
|
|
|
|
|
|
sub _get_country_from { |
395
|
15
|
|
|
15
|
|
25
|
my ($self,$str_or_obj) = @_; |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# $str_or_arg should be |
398
|
|
|
|
|
|
|
# - Number::MuPhone instance |
399
|
|
|
|
|
|
|
# - E.123 formatted number |
400
|
|
|
|
|
|
|
# - 2 char country code |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
# muphone num |
403
|
15
|
100
|
|
|
|
71
|
if (ref $str_or_obj eq 'Number::MuPhone') { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
404
|
8
|
|
|
|
|
191
|
return $str_or_obj->country; |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
# E.123 |
407
|
|
|
|
|
|
|
elsif ($str_or_obj =~ /^\s\+/) { |
408
|
0
|
|
|
|
|
0
|
my $num = Number::MuPhone->new($str_or_obj); |
409
|
0
|
|
|
|
|
0
|
return $num->country; |
410
|
|
|
|
|
|
|
} |
411
|
|
|
|
|
|
|
# it should be a country |
412
|
|
|
|
|
|
|
elsif ( $str_or_obj =~ /^[A-Z]{2}$/ ) { |
413
|
7
|
|
|
|
|
18
|
return $str_or_obj; |
414
|
|
|
|
|
|
|
} |
415
|
|
|
|
|
|
|
else { |
416
|
0
|
|
|
|
|
0
|
die "Not a country, E.123 num, or MuPhone object: $str_or_obj"; |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
=head1 METHODS |
421
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=head2 dial_from |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
How to dial the number from the number/country sent in as an arg. eg |
425
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
my $uk_num1 = Number::MuPhone->new({ country => 'GB', number => '01929 552699' }); |
427
|
|
|
|
|
|
|
my $uk_num2 = Number::MuPhone->new({ country => 'GB', number => '01929 552698' }); |
428
|
|
|
|
|
|
|
my $us_num = Number::MuPhone->new({ country => 'US', number => '203 503 1234' }); |
429
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
# these all have the same output (01929552699) |
431
|
|
|
|
|
|
|
my $dial_from_uk = $uk_num1->dial_from($uk_num2); |
432
|
|
|
|
|
|
|
my $dial_from_uk = $uk_num1->dial_from('GB'); |
433
|
|
|
|
|
|
|
my $dial_from_uk = $uk_num1->dial_from('+441929 552698'); |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
# similarly, dialling the number from the US (011441929552699) |
436
|
|
|
|
|
|
|
my $dial_from_us = $uk_num1->dial_from($us_num); |
437
|
|
|
|
|
|
|
my $dial_from_us = $uk_num1->dial_from('US'); |
438
|
|
|
|
|
|
|
my $dial_from_us = $uk_num1->dial_from('+1 203 503 1234'); |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
=cut |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
sub dial_from { |
443
|
7
|
|
|
7
|
1
|
2754
|
my ($self,$str_or_obj) = @_; |
444
|
7
|
|
33
|
|
|
19
|
$str_or_obj||=$self; |
445
|
7
|
|
|
|
|
15
|
my $from_country = $self->_get_country_from($str_or_obj); |
446
|
7
|
100
|
|
|
|
151
|
if ( $from_country eq $self->country ) { |
447
|
4
|
|
|
|
|
83
|
return $self->national_dial; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
else { |
450
|
|
|
|
|
|
|
return $MUPHONE_DATA->{territories}->{ $from_country }->{internationalPrefix} |
451
|
3
|
|
|
|
|
65
|
.$self->country_code |
452
|
|
|
|
|
|
|
.$self->_cleaned_number; |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=head2 display_from |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
How to display the number for the number/country sent in as an arg. eg |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
my $uk_num1 = Number::MuPhone->new({ country => 'GB', number => '01929 552699' }); |
461
|
|
|
|
|
|
|
my $uk_num2 = Number::MuPhone->new({ country => 'GB', number => '01929 552698' }); |
462
|
|
|
|
|
|
|
my $us_num = Number::MuPhone->new({ country => 'US', number => '203 503 1234' }); |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
# these all have the same output (01929 552699) |
465
|
|
|
|
|
|
|
my $display_from_uk = $uk_num1->display_from($uk_num2); |
466
|
|
|
|
|
|
|
my $display_from_uk = $uk_num1->display_from('GB'); |
467
|
|
|
|
|
|
|
my $display_from_uk = $uk_num1->display_from('+441929 552698'); |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# similarly, dialling the number from the US (01144 1929 552699) |
470
|
|
|
|
|
|
|
my $display_from_us = $uk_num1->display_from($us_num); |
471
|
|
|
|
|
|
|
my $display_from_us = $uk_num1->display_from('US'); |
472
|
|
|
|
|
|
|
my $display_from_us = $uk_num1->display_from('+1 203 503 1234'); |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
=cut |
475
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
sub display_from { |
477
|
8
|
|
|
8
|
1
|
2205
|
my ($self,$str_or_obj) = @_; |
478
|
8
|
|
33
|
|
|
20
|
$str_or_obj||=$self; |
479
|
8
|
|
|
|
|
19
|
my $from_country = $self->_get_country_from($str_or_obj); |
480
|
8
|
100
|
|
|
|
172
|
if ( $from_country eq $self->country ) { |
481
|
4
|
|
|
|
|
82
|
return $self->national_display; |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
else { |
484
|
|
|
|
|
|
|
# (DIAL PREFIX) (COUNTRY CODE) (FORMATTED NUMBER) [ (EXTENSION) ] |
485
|
|
|
|
|
|
|
return $MUPHONE_DATA->{territories}->{ $from_country }->{internationalPrefix} |
486
|
4
|
|
|
|
|
105
|
.$self->country_code.' ' |
487
|
|
|
|
|
|
|
.$self->_formatted_number.$self->_extension_display; |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
} |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
# PRIVATE METHODS |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
# splits off optional extension, and cleans both up for storage |
495
|
|
|
|
|
|
|
# only place where we set error |
496
|
|
|
|
|
|
|
sub _process_raw_number { |
497
|
17
|
|
|
17
|
|
22
|
my $self = shift; |
498
|
|
|
|
|
|
|
|
499
|
17
|
|
|
|
|
123
|
my ($raw_num,$ext) = split $EXTENSION_REGEX, $self->number; |
500
|
17
|
|
100
|
|
|
73
|
$ext||=''; |
501
|
17
|
|
|
|
|
23
|
$ext =~ s/\D//g; |
502
|
17
|
|
|
|
|
43
|
$self->extension($ext); |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
# if number begins with a '+' we can determine country from E.123 number |
505
|
17
|
100
|
|
|
|
285
|
if ($raw_num =~ /^\s*\+/) { |
|
|
50
|
|
|
|
|
|
506
|
4
|
|
|
|
|
13
|
$self->_process_from_e123($raw_num); |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
# if we have a country set, clean up raw number (ie, strip national dial code, if set) |
509
|
|
|
|
|
|
|
elsif (my $country = $self->country) { |
510
|
13
|
|
|
|
|
119
|
$raw_num =~ s/\D//g; |
511
|
13
|
|
|
|
|
37
|
my $national_prefix = $MUPHONE_DATA->{territories}->{ $country }->{nationalPrefix}; |
512
|
13
|
50
|
|
|
|
26
|
if ( defined $national_prefix ) { |
513
|
13
|
|
|
|
|
83
|
$raw_num =~ s/^$national_prefix//; |
514
|
|
|
|
|
|
|
} |
515
|
13
|
|
|
|
|
40
|
$self->_cleaned_number( $raw_num ); |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
|
|
|
|
|
|
# if no country set by the time we get here, we need to set error and bail |
519
|
17
|
|
|
|
|
279
|
my $country = $self->country; |
520
|
17
|
50
|
|
|
|
101
|
unless ( $country ) { |
521
|
0
|
|
|
|
|
0
|
$self->error("Country not supplied, and I can't determine it from the number"); |
522
|
0
|
|
|
|
|
0
|
return; |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
|
525
|
|
|
|
|
|
|
# Number must match the national number pattern, if exists |
526
|
17
|
|
|
|
|
36
|
my $cleaned_num = $self->_cleaned_number; |
527
|
17
|
50
|
33
|
|
|
75
|
if ( $MUPHONE_DATA->{territories}->{ $country }->{generalDesc} |
528
|
|
|
|
|
|
|
&& $MUPHONE_DATA->{territories}->{ $country }->{generalDesc}->{nationalNumberPattern} ) { |
529
|
|
|
|
|
|
|
|
530
|
17
|
|
|
|
|
169
|
my $regex = qr/^(?:$MUPHONE_DATA->{territories}->{ $country }->{generalDesc}->{nationalNumberPattern})$/; |
531
|
17
|
100
|
|
|
|
102
|
unless ( $cleaned_num =~ $regex ) { |
532
|
3
|
|
|
|
|
59
|
$self->error("Number ($cleaned_num) is not valid for country ($country)"); |
533
|
3
|
|
|
|
|
108
|
return; |
534
|
|
|
|
|
|
|
} |
535
|
|
|
|
|
|
|
} |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
# confirm cleaned number is a valid number for the country |
538
|
14
|
50
|
|
|
|
249
|
unless ( $self->_cleaned_number =~ $self->_national_number_regex ) { |
539
|
0
|
|
|
|
|
0
|
$self->error("Number $raw_num is not valid for country ".$self->country); |
540
|
|
|
|
|
|
|
} |
541
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
# don't create formatted number if we have an error |
543
|
14
|
50
|
|
|
|
236
|
$self->error and return; |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# if no number formats, just set to the cleaned number |
546
|
14
|
|
|
|
|
286
|
my $number_formats = $MUPHONE_DATA->{territories}->{ $self->country }->{availableFormats}->{numberFormat}; |
547
|
|
|
|
|
|
|
|
548
|
14
|
|
|
|
|
91
|
my $num = $self->_cleaned_number; |
549
|
14
|
|
|
|
|
18
|
my $national_prefix_optional=0; |
550
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
# iterate through the available formats until you get a match |
552
|
|
|
|
|
|
|
# (if not set, we default to cleaned number |
553
|
14
|
|
|
|
|
27
|
FORMAT: foreach my $format_hash (@$number_formats) { |
554
|
|
|
|
|
|
|
# not all countries have leading digit mappings |
555
|
49
|
50
|
|
|
|
105
|
if (my $leading_digits = $format_hash->{leadingDigits}) { |
556
|
49
|
100
|
|
|
|
740
|
next FORMAT unless ( $num =~ /^(?:$leading_digits)/ ); |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
14
|
|
|
|
|
197
|
my $pattern = qr/^$format_hash->{pattern}$/; |
560
|
14
|
50
|
|
|
|
90
|
next FORMAT unless ( $num =~ $pattern ); |
561
|
|
|
|
|
|
|
|
562
|
14
|
|
|
|
|
28
|
my $format = $format_hash->{format}; |
563
|
|
|
|
|
|
|
|
564
|
14
|
|
|
|
|
36
|
my $regex_statement = "\$num =~ s/$pattern/$format/;"; |
565
|
14
|
|
|
|
|
1339
|
eval $regex_statement; |
566
|
14
|
50
|
|
|
|
60
|
if ($@) { |
567
|
0
|
|
|
|
|
0
|
$self->error("Can't format number($num) with regex($regex_statement): $@"); |
568
|
0
|
|
|
|
|
0
|
last FORMAT; |
569
|
|
|
|
|
|
|
} |
570
|
|
|
|
|
|
|
|
571
|
|
|
|
|
|
|
$national_prefix_optional = $format_hash->{nationalPrefixOptionalWhenFormatting} |
572
|
14
|
100
|
|
|
|
40
|
? 1 : 0; |
573
|
14
|
|
|
|
|
38
|
last FORMAT; |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
|
576
|
14
|
|
|
|
|
62
|
$self->_formatted_number($num); |
577
|
14
|
|
|
|
|
90
|
$self->_national_prefix_optional_when_formatting($national_prefix_optional); |
578
|
|
|
|
|
|
|
|
579
|
|
|
|
|
|
|
} |
580
|
|
|
|
|
|
|
|
581
|
|
|
|
|
|
|
# number starts with a + ? Great, we should be able to work it out. |
582
|
|
|
|
|
|
|
sub _process_from_e123 { |
583
|
4
|
|
|
4
|
|
7
|
my ($self,$num) = @_; |
584
|
|
|
|
|
|
|
|
585
|
4
|
|
|
|
|
20
|
$num =~ s/\D//g; |
586
|
|
|
|
|
|
|
|
587
|
4
|
|
|
|
|
7
|
my $countries = []; |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
# grab from country lookup - country code is 1-3 digits long |
590
|
4
|
|
|
|
|
9
|
my @prefixes = map { substr($num, 0, $_) } 1..3; |
|
12
|
|
|
|
|
28
|
|
591
|
4
|
|
|
|
|
9
|
PREFIX: foreach my $idd (@prefixes) { |
592
|
|
|
|
|
|
|
# we found a match |
593
|
6
|
100
|
|
|
|
20
|
if ($countries = $MUPHONE_DATA->{idd_codes}->{$idd}) { |
594
|
|
|
|
|
|
|
# so strip off the IDD from the number |
595
|
4
|
|
|
|
|
55
|
$num =~ s/^$idd//; |
596
|
4
|
|
|
|
|
11
|
last PREFIX; |
597
|
|
|
|
|
|
|
} |
598
|
|
|
|
|
|
|
} |
599
|
|
|
|
|
|
|
|
600
|
|
|
|
|
|
|
# now find out which country the number matches |
601
|
|
|
|
|
|
|
# (for IDD codes with multiple countries, this may not be correct, but should be |
602
|
|
|
|
|
|
|
# good enough for this use case - just don't rely on the country |
603
|
|
|
|
|
|
|
# TODO - maybe iterate through all regexes by number type to confirm validity? |
604
|
|
|
|
|
|
|
# generalDesc regex is too loose for (eg) US/CA |
605
|
|
|
|
|
|
|
# to implement this, we'd need to keep the various number type regexes around |
606
|
|
|
|
|
|
|
# Suggest look at adding in next update |
607
|
4
|
|
|
|
|
6
|
my $country; |
608
|
4
|
|
|
|
|
9
|
COUNTRY: foreach my $country (@$countries) { |
609
|
|
|
|
|
|
|
my $national_number_format_regex = $MUPHONE_DATA->{territories}->{$country}->{generalDesc} && $MUPHONE_DATA->{territories}->{$country}->{generalDesc}->{nationalNumberPattern} |
610
|
58
|
50
|
33
|
|
|
1209
|
? qr/^$MUPHONE_DATA->{territories}->{$country}->{generalDesc}->{nationalNumberPattern}$/ |
611
|
|
|
|
|
|
|
: ''; |
612
|
58
|
50
|
|
|
|
152
|
$national_number_format_regex |
613
|
|
|
|
|
|
|
or next COUNTRY; |
614
|
|
|
|
|
|
|
|
615
|
58
|
100
|
|
|
|
305
|
$num =~ $national_number_format_regex |
616
|
|
|
|
|
|
|
or next COUNTRY; |
617
|
|
|
|
|
|
|
|
618
|
6
|
|
|
|
|
131
|
$self->country($country); |
619
|
6
|
|
|
|
|
206
|
$self->_cleaned_number($num); |
620
|
|
|
|
|
|
|
} |
621
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
} |
623
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
1; |