File Coverage

blib/lib/URI/tel.pm
Criterion Covered Total %
statement 262 367 71.3
branch 124 194 63.9
condition 37 71 52.1
subroutine 28 40 70.0
pod 17 26 65.3
total 468 698 67.0


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## URI::tel - ~/lib/URI/tel.pm
3             ## Version v0.800.1
4             ## Copyright(c) 2019 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <@sitael.tokyo.deguest.jp>
6             ## Created 2016/02/12
7             ## Modified 2019/08/30
8             ##
9             ##----------------------------------------------------------------------------
10             package URI::tel;
11             BEGIN
12 0         0 {
13 3     3   70267 use strict;
  3         16  
  3         93  
14 3     3   1346 use parent 'URI';
  3         920  
  3         14  
15 3     3   266 our( $VERSION, $VERBOSE, $DEBUG, $ERROR );
16 3         5 our( $RESERVED, $MARK, $UNRESERVED, $PCT_ENCODED, $URIC, $ALPHA, $DIGIT, $ALPHANUM, $HEXDIG );
17 3         4 our( $PARAM_UNRESERVED, $VISUAL_SEPARATOR, $PHONEDIGIT, $GLOBAL_NUMBER_DIGITS, $PARAMCHAR, $DOMAINLABEL, $TOPLABEL, $DOMAINNAME, $DESCRIPTOR, $PNAME, $PVALUE, $PARAMETER, $EXTENSION, $ISDN_SUBADDRESS, $CONTEXT, $PAR, $PHONEDIGIT_HEX, $GLOBAL_NUMBER, $LOCAL_NUMBER, $OTHER, $TEL_SUBSCRIBER, $TEL_URI );
18 3         5 our( $COUNTRIES, $IDD_RE );
19 3         7669 $VERSION = 'v0.800.1';
20             use overload ('""' => 'as_string',
21 0     0   0 '==' => sub { _obj_eq(@_) },
22 0     0   0 '!=' => sub { !_obj_eq(@_) },
23 3         28 fallback => 1,
24 3     3   25658 );
  3         7  
25             };
26              
27             {
28             #$RESERVED = qr{[\[\;\/\?\:\@\&\=\+\$\,\[\]]+};
29             $RESERVED = q{[;/?:@&=+$,[]+};
30             $MARK = q{-_.!~*'()}; #'; emacs
31             $UNRESERVED = qq{A-Za-z0-9\Q$MARK\E};
32             ## "%" HEXDIG HEXDIG
33             $PCT_ENCODED = qr{\%[0-9A-Fa-f]{2}};
34             #$URIC = quotemeta( $RESERVED ) . $UNRESERVED . "%";
35             $URIC = qr{(?:[\Q$RESERVED\E]+|[$UNRESERVED]+|(?:$PCT_ENCODED)+)};
36             # $ALPHA = qr{A-Za-z};
37             # $DIGIT = qr{\d};
38             $ALPHA = qr{A-Za-z};
39             $DIGIT = qq{0-9};
40             # $ALPHANUM = qr{A-Za-z\d};
41             $ALPHANUM = qq{A-Za-z0-9};
42             # $HEXDIG = qr{\dA-F};
43             $HEXDIG = qr{\dA-F};
44             # $PARAM_UNRESERVED = qr{[\[\]\/\:\&\+\$]+};
45             $PARAM_UNRESERVED = q{[]/:&+$};
46             $VISUAL_SEPARATOR = q(-.());
47             ## DIGIT / [ visual-separator ]
48             # $PHONEDIGIT = qr{$DIGIT\Q$VISUAL_SEPARATOR\E};
49             $PHONEDIGIT = qq{$DIGIT\Q$VISUAL_SEPARATOR\E};
50             ## "+" *phonedigit DIGIT *phonedigit
51             $GLOBAL_NUMBER_DIGITS = qr{\+[$PHONEDIGIT]*[$DIGIT]+[$PHONEDIGIT]*};
52             ## param-unreserved / unreserved / pct-encoded
53             $PARAMCHAR = qr{(?:[\Q$PARAM_UNRESERVED\E]+|[$UNRESERVED]+|(?:$PCT_ENCODED)+)};
54             ## alphanum / alphanum *( alphanum / "-" ) alphanum
55             $DOMAINLABEL = qr{(?:[$ALPHANUM]+|[$ALPHANUM]+(?:[$ALPHANUM\-]+)*[$ALPHANUM]+)};
56             ## ALPHA / ALPHA *( alphanum / "-" ) alphanum
57             $TOPLABEL = qr{(?:[$ALPHA]+|[$ALPHA]+[$ALPHANUM\-]*[$ALPHANUM]+)};
58             ## *( domainlabel "." ) toplabel [ "." ]
59             $DOMAINNAME = qr{(?:$DOMAINLABEL\.)*(?:$TOPLABEL\.?)+};
60             ## domainname / global-number-digits
61             $DESCRIPTOR = qr{(?:$DOMAINNAME|$GLOBAL_NUMBER_DIGITS)};
62             ## 1*( alphanum / "-" )
63             $PNAME = qr{[$ALPHANUM\-]+};
64             ## 1*paramchar
65             $PVALUE = qr{(?:$PARAMCHAR)};
66             ## ";" pname ["=" pvalue ]
67             $PARAMETER = qr{\;$PNAME=$PVALUE};
68             ## ";ext=" 1*phonedigit
69             ##$EXTENSION = qr{\;ext=[$PHONEDIGIT]+}msxi;
70             ## Tweaking the rfc regular expression to add often used extension format
71             ## See: https://discussions.apple.com/thread/1635858?start=0&tstart=0
72             ## or
73             ## http://stackoverflow.com/questions/2403767/string-format-phone-numbers-with-extension
74             $EXTENSION = qr{(?:[\;\,]?ext[\=\.]?|x)[$PHONEDIGIT]+}msxi;
75             ## ";isub=" 1*uric
76             $ISDN_SUBADDRESS = qr{\;isub=$URIC}msxi;
77             ## ";phone-context=" descriptor
78             $CONTEXT = qr{\;phone-context=$DESCRIPTOR}msxi;
79             ## parameter / extension / isdn-subaddress
80             $PAR = qr{(?:(?:($PARAMETER)|($EXTENSION)|($ISDN_SUBADDRESS)))+};
81             ## HEXDIG / "*" / "#" / [ visual-separator ]
82             $PHONEDIGIT_HEX = qr{[$HEXDIG\*\#$VISUAL_SEPARATOR]+};
83             ## *phonedigit-hex (HEXDIG / "*" / "#")*phonedigit-hex
84             $LOCAL_NUMBER_DIGITS = qr{(?:$PHONEDIGIT_HEX)?[$HEXDIG\*\#]+(?:$PHONEDIGIT_HEX)?};
85             ## global-number-digits *par => "+" *phonedigit DIGIT *phonedigit
86             $GLOBAL_NUMBER = qr{($GLOBAL_NUMBER_DIGITS)($PAR)*};
87             ## local-number-digits *par context *par
88             $LOCAL_NUMBER = qr{($LOCAL_NUMBER_DIGITS)($PAR)*($CONTEXT)($PAR)*};
89             ## This is a non-rfc standard requirement, but a necessity to catch local number with no context
90             ## such as 03-1234-5678 plain and simple
91             $OTHER = qr{(\+?[$PHONEDIGIT]*[$DIGIT]+[$PHONEDIGIT]*)($PAR)*};
92             ## Like +1-800-LAWYR-UP => +1-800-52997-87
93             $VANITY = qr{(\+?[$PHONEDIGIT]*[A-Z0-9\Q$VISUAL_SEPARATOR\E]+[$PHONEDIGIT]*)};
94             $TEL_SUBSCRIBER = qr{(?:$VANITY|$GLOBAL_NUMBER|$LOCAL_NUMBER|$OTHER)}xs;
95             ##$TEL_SUBSCRIBER = qr{(?:$GLOBAL_NUMBER)};
96             $TEL_URI = qr{(?:tel\:)?$TEL_SUBSCRIBER};
97             # https://tools.ietf.org/search/rfc3966#section-3
98            
99             $COUNTRIES = {};
100             }
101              
102             sub _init
103             {
104 0     0   0 my $class = shift( @_ );
105 0         0 my( $str, $scheme ) = @_;
106             # find all funny characters and encode the bytes.
107 0         0 $str = $class->_uric_escape( $str );
108 0 0 0     0 $str = "$scheme:$str" unless $str =~ /^[a-zA-Z][a-zA-Z0-9.+\-]*:/o ||
109             $class->_no_scheme_ok;
110             #my $self = bless \$str, $class;
111             #$self;
112 0         0 return( $class->new( $str ) );
113             }
114              
115             sub new
116             {
117 290     290 1 146212 my $this = shift( @_ );
118 290         471 my $str = shift( @_ );
119 290   66     1038 my $class = ref( $this ) || $this;
120 290         389 my $orig = $str;
121 290         733 $str =~ s/[[:blank:]]+//gs;
122 290         462 my $temp = {};
123 290         466 my @matches = ();
124 290         352 my @names = ();
125 290 100       5015 if( @matches = $str =~ /^((?:tel\:)?$GLOBAL_NUMBER)$/ )
    100          
    100          
    100          
126             {
127 262         643 @names = qw( all subscriber params last_param );
128 262         546 $temp->{ 'type' } = 'global';
129             }
130             elsif( @matches = $str =~ /^((?:tel\:)?$LOCAL_NUMBER)$/ )
131             {
132 5         15 $temp->{ 'type' } = 'local';
133 5         13 @names = qw( all subscriber params1 last_param1 ignore5 ignore6 context params2 last_param2 ignore10 ignore11 );
134 5 50       13 $temp->{ '_has_context_param' } = 1 if( length( $matches[6] ) );
135 5         8 $temp->{ 'local_number' } = $str;
136 5         16 $temp->{ 'local_number' } =~ s/^tel\://;
137             }
138             ## e.g. 911, 110 or just ordinary local phones like 03-1234-5678
139             elsif( @matches = $str =~ /^((?:tel\:)?$OTHER)$/ )
140             {
141 19         50 $temp->{ 'type' } = 'other';
142 19         68 @names = qw( all subscriber params ignore4 last_param );
143             }
144             ## e.g. +1-800-LAWYR-UP
145             elsif( @matches = $str =~ /^((?:tel\:)?$VANITY)$/ )
146             {
147 2         7 $temp->{ 'type' } = 'vanity';
148 2         4 @names = qw( all subscriber );
149             }
150             else
151             {
152 2         7 $ERROR = "Unknown telephone number '$str'.";
153 2         31 warn( $ERROR );
154             }
155            
156             ## The component name for each match
157 290         1036 @$temp{ @names } = @matches;
158            
159 290 100       913 $temp->{ 'params' } = $temp->{ 'params1' } ? $temp->{ 'params1' } : $temp->{ 'params2' } if( !length( $temp->{ 'params' } ) );
    100          
160 290         583 $temp->{ 'context' } =~ s/;[^=]+=(.*?)$/$1/gs;
161            
162 290 100 100     877 if( $str =~ /^(?:tel\:)?\+/ )
    100 66        
163             {
164 263         601 $temp->{ 'intl_code' } = $this->_extract_intl_code( $str );
165             }
166             elsif( $temp->{ 'context' } &&
167             $temp->{ 'context' } !~ /^[a-zA-Z]/ &&
168             substr( $temp->{ 'context' }, 0, 1 ) eq '+' )
169             {
170 2         7 $temp->{ 'intl_code' } = $this->_extract_intl_code( $temp->{ 'context' } );
171             ## We flag it as extracted from context, because we do not want to prepend the subscriber number with it.
172             ## It's just too dangerous as we cannot tell the subscriber number is actually a proper number that can be dialed from outside e.g. 911 or 110 are emergency number who may heva a context with international code
173             ## However, if the international code was provided by the user then that's his responsibility
174             ## If the user wants to just provide some context, then better to use context() instead.
175             ## Knowing the international code helps getting some other useful information, but it should not necessarily affect the format of the number
176 2         4 $temp->{ '_intl_code_from_context' } = 1;
177             }
178 290 100 100     1321 $temp->{ 'context' } = '+' . $temp->{ 'intl_code' } if( !length( $temp->{ 'context' } ) && length( $temp->{ 'intl_code' } ) );
179            
180 290 50 66     947 if( $temp->{ 'type' } eq 'global' && $temp->{ 'intl_code' } )
181             {
182 262         573 $temp->{ 'local_number' } = $this->_extract_local_number( $temp->{ 'intl_code' }, $temp->{ 'subscriber' } );
183             }
184            
185             my $hash = {
186             'original' => ( $orig ne $str ) ? $orig : $temp->{ 'all' },
187             'is_global' => $temp->{ 'type' } eq 'global' ? 1 : 0,
188             'is_local' => ( $temp->{ 'type' } eq 'local' or $temp->{ 'type' } eq 'other' ) ? 1 : 0,
189             'is_other' => $temp->{ 'type' } eq 'other' ? 1 : 0,
190             'is_vanity' => $temp->{ 'type' } eq 'vanity' ? 1 : 0,
191             'subscriber' => $temp->{ 'subscriber' },
192             'params' => $temp->{ 'params' },
193             'last_param' => $temp->{ 'last_param' } ? $temp->{ 'last_param' } : $temp->{ 'last_param1' } ? $temp->{ 'last_param1' } : $temp->{ 'last_param2' },
194             'context' => $temp->{ 'context' },
195             'intl_code' => $temp->{ 'intl_code' },
196             '_intl_code_from_context' => $temp->{ '_intl_code_from_context' },
197 290 100 100     3379 'local_number' => $temp->{ 'local_number' },
    100          
    100          
    100          
    100          
    100          
    100          
198             };
199 290         566 my $self = bless( $hash, $class );
200 290         421 my $prams = [];
201 290 100       553 if( length( $temp->{ 'params' } ) )
202             {
203 10         15 my $pram_str = $temp->{ 'params' };
204 10         28 $pram_str =~ s/^[\.\,\#\;]//;
205 10         28 $prams = [ $self->split_str( $pram_str ) ];
206             }
207             ## Private parameters
208 290         385 my $priv = {};
209 290         557 foreach my $this ( @$prams )
210             {
211 16         64 $this =~ s/^(x|ext)\.?(\d+)$/ext=$2/i;
212 16         46 my( $p, $v ) = split( /=/, $this, 2 );
213 16         29 $p =~ s/\-/\_/gs;
214 16 100       57 if( lc( $p ) =~ /^(ext|isdn_subaddress)$/ )
    100          
215             {
216 10         29 $hash->{ lc( $p ) } = $v;
217             }
218             elsif( lc( $p ) eq 'phone_context' )
219             {
220 2         4 $hash->{ 'context' } = $v;
221 2         5 $temp->{ '_has_context_param' } = 1;
222             }
223             else
224             {
225 4         11 $priv->{ lc( $p ) } = $v;
226             }
227             }
228 290         643 $self->{ 'private' } = $priv;
229 290 100 100     1064 $self->{ 'ext' } = $temp->{ 'ext' } if( !length( $hash->{ 'ext' } ) && !$self->{ 'is_vanity' } );
230 290         487 $self->{ 'ext' } =~ s/\D//gs;
231             ## Because a context may be +81 or it could be a domain name example.com
232             ## if we had it as a parameter at instantiation, we remember it and honour it when we stringify
233 290 100       556 $self->{ '_prepend_context' } = $temp->{ '_has_context_param' } ? 0 : 1;
234 290         1353 return( $self );
235             }
236              
237             sub as_string
238             {
239 49     49 1 16760 my $self = shift( @_ );
240 49 100       123 return( $self->{ 'cache' } ) if( length( $self->{ 'cache' } ) );
241 47 100       149 my @uri = ( 'tel:' . $self->{ 'subscriber' } ) if( length( $self->{ 'subscriber' } ) );
242 47         59 my @params = ();
243 47 100       133 push( @params, sprintf( "ext=%s", $self->{ 'ext' } ) ) if( length( $self->{ 'ext' } ) );
244 47 50       93 push( @params, sprintf( "isub=%s", $self->{ 'isdn_subaddress' } ) ) if( length( $self->{ 'isdn_subaddress' } ) );
245 47 100       77 if( length( $self->{ 'context' } ) )
246             {
247 31 100 66     121 if( !$self->{ '_prepend_context' } )
    100          
248             {
249 15         53 push( @params, sprintf( "phone-context=%s", $self->{ 'context' } ) );
250             }
251             ## Context is not some domain name
252             elsif( $self->{ 'subscriber' } !~ /^\+\d+/ && $self->{ 'context' } !~ /[a-zA-Z]+/ )
253             {
254 1         4 @uri = ( 'tel:' . $self->{ 'context' } . '-' . $self->{ 'subscriber' } );
255             }
256             }
257 47 100 100     135 if( length( $self->{ 'intl_code' } ) && !$self->{ '_intl_code_from_context' } )
258             {
259 19 0 33     59 if( $self->{ 'subscriber' } !~ /^\+\d+/ && $uri[0] !~ /^tel\:\+/ )
260             {
261 0         0 @uri = ( 'tel:' . '+' . $self->{ 'intl_code' } . '-' . $self->{ 'subscriber' } );
262             }
263             }
264 47         68 my $priv = $self->{ 'private' };
265 47         127 foreach my $k ( sort( keys( %$priv ) ) )
266             {
267 8 50       28 push( @params, sprintf( "$k=%s", $priv->{ $k } ) ) if( length( $priv->{ $k } ) );
268             }
269 47 100       110 push( @uri, join( ';', @params ) ) if( scalar( @params ) );
270 47         146 $self->{ 'cache' } = join( ';', @uri );
271 47         345 return( $self->{ 'cache' } );
272             }
273              
274             *letters2digits = \&aton;
275              
276             sub aton
277             {
278 21     21 1 22 my $self = shift( @_ );
279 21   66     93 my $str = shift( @_ ) || $self->{ 'subscriber' };
280 21         30 my $letters = 'abcdefghijklmnopqrstuvwxyz';
281 21         21 my $digits = '22233344455566677778889999';
282 21 100 66     94 return( $str ) if( $str !~ /[a-zA-Z]+/ || !$self->is_vanity );
283 2         5 $str = lc( $str );
284 2         5 my $res = '';
285 2         7 for( my $i = 0; $i < length( $str ); $i++ )
286             {
287 26         60 my $c = substr( $str, $i, 1 );
288 26         70 my $p = index( $letters, $c );
289 26 100       64 $res .= $p != -1 ? substr( $digits, $p, 1 ) : $c;
290             }
291 2         5 return( $res );
292             }
293              
294             sub canonical
295             {
296 21     21 1 1300 my $self = shift( @_ );
297 21         47 my $tel = $self->aton;
298 21         145 $tel =~ s/[\Q$VISUAL_SEPARATOR\E]+//gs;
299 21         58 my $uri = $self->new( "tel:$tel" );
300 21 100       73 $uri->ext( $self->{ 'ext' } ) if( length( $self->{ 'ext' } ) );
301 21 50       38 $uri->isub( $self->{ 'isdn_subaddress' } ) if( length( $self->{ 'isdn_subaddress' } ) );
302 21 100       50 $uri->context( $self->{ 'context' } ) if( length( $self->{ 'context' } ) );
303 21 100       50 $uri->international_code( $self->{ 'intl_code' } ) if( length( $self->{ 'intl_code' } ) );
304 21         36 $uri->{ '_has_context_param' } = $self->{ '_has_context_param' };
305 21         27 $uri->{ '_prepend_context' } = $self->{ '_prepend_context' };
306 21         30 $uri->{ '_intl_code_from_context' } = $self->{ '_intl_code_from_context' };
307 21         34 $uri->{ 'local_number' } = $self->{ 'local_number' };
308 21         23 my $priv = $self->{ 'private' };
309 21         40 %{$uri->{ 'private' }} = %$priv;
  21         47  
310 21         80 return( $uri );
311             }
312              
313             sub cc2context
314             {
315 1     1 0 7 my $self = shift( @_ );
316 1         3 my $cc = uc( shift( @_ ) );
317 1 50       3 return( $self->error( "No country code provided." ) ) if( !length( $cc ) );
318 1         33 $self->_load_countries;
319 1         1 my $hash = $COUNTRIES;
320 1         144 foreach my $k ( sort( keys( %$hash ) ) )
321             {
322             ## array ref
323 205         210 my $ref = $hash->{ $k };
324 205         219 foreach my $this ( @$ref )
325             {
326 217 100       383 if( $this->{ 'cc' } eq $cc )
327             {
328 1         6 return( '+' . $k );
329             }
330             }
331             }
332             ## Nothing found
333 0         0 return( '' );
334             }
335              
336             sub clone
337             {
338 0     0 1 0 my $self = shift( @_ );
339 0         0 my $class = ref( $self );
340 0         0 my $hash = {};
341 0         0 my @keys = keys( %$self );
342 0         0 @$hash{ @keys } = @$self{ @keys };
343 0         0 delete( $hash->{ 'cache' } );
344 0         0 return( bless( $hash, $class ) );
345             }
346              
347             sub context
348             {
349 280     280 1 1296 my $self = shift( @_ );
350 280 100       527 if( @_ )
351             {
352 14         29 my $str = shift( @_ );
353 14 50       265 if( $str !~ /^$DESCRIPTOR$/ )
354             {
355 0         0 warn( "'$str' is not a valid context\n" );
356 0         0 return( undef() );
357             }
358 14         27 delete( $self->{ 'cache' } );
359 14         36 $self->{ '_has_context_param' } = 1;
360 14         19 $self->{ '_prepend_context' } = 0;
361 14         17 $self->{ 'context' } = $str;
362             ## We found an international country code in the prefix provided, so let's set it
363 14 100 100     42 if( !length( $self->{ 'intl_code' } ) && ( my $code = $self->_extract_intl_code( $str ) ) )
364             {
365 3         8 $self->{ '_intl_code_from_context' } = 1;
366 3         5 $self->{ 'intl_code' } = $code;
367             }
368             }
369 280         696 return( $self->{ 'context' } );
370             }
371              
372             sub country
373             {
374 266     266 1 338 my $self = shift( @_ );
375 3     3   25 no overloading;
  3         4  
  3         5050  
376 266         602 $self->_load_countries;
377 266         369 my $hash = $COUNTRIES;
378 266         516 my $code = $self->international_code;
379 266 50       595 return( wantarray() ? () : [] ) if( !length( $code ) );
    100          
380 255         1380 $code =~ s/[\Q$VISUAL_SEPARATOR\E]+//g;
381 255 50       1608 if( $code =~ /^\+($IDD_RE)/ )
382             {
383 0         0 $code = $1;
384             }
385 255 0       598 return( wantarray() ? () : [] ) if( !exists( $hash->{ $code } ) );
    50          
386 255         739 my $ref = $hash->{ $code };
387 255 50       899 return( wantarray() ? @$ref : \@$ref );
388             }
389              
390             sub country_code
391             {
392 245     245 0 130138 my $self = shift( @_ );
393 245         482 my $ref = $self->country_codes;
394 245 50       464 return( '' ) if( ref( $ref ) ne 'ARRAY' );
395 245         845 return( $ref->[0] );
396             }
397              
398             sub country_codes
399             {
400 245     245 0 339 my $self = shift( @_ );
401 245         507 my $ref = $self->country;
402 245 50       615 return( '' ) if( ref( $ref ) ne 'ARRAY' );
403 245         1080 my @codes = map( $_->{cc}, @$ref );
404 245 50       587 return( wantarray() ? @codes : \@codes );
405             }
406              
407             sub country_name
408             {
409 0     0 0 0 my $self = shift( @_ );
410 0         0 my $ref = $self->country_names;
411 0 0       0 return( '' ) if( ref( $ref ) ne 'ARRAY' );
412 0         0 return( $ref->[0] );
413             }
414              
415             sub country_names
416             {
417 0     0 0 0 my $self = shift( @_ );
418 0         0 my $ref = $self->country;
419 0 0       0 return( '' ) if( ref( $ref ) ne 'ARRAY' );
420 0         0 my @names = map( $_->{name}, @$ref );
421 0 0       0 return( wantarray() ? @names : \@names );
422             }
423              
424             sub error
425             {
426 0     0 0 0 my $self = shift( @_ );
427 0         0 my $level = 0;
428 0         0 my $caller = caller;
429 0         0 my $err = join( '', @_ );
430 0   0     0 my $class = ref( $self ) || $self;
431 0 0 0     0 if( $err && length( $err ) )
432             {
433 0         0 my( $frame, $caller ) = ( $level, '' );
434 0         0 while( $caller = ( caller( $frame ) )[ 0 ] )
435             {
436 0 0       0 last if( $caller ne 'URI::tel' );
437 0         0 $frame++;
438             }
439 0         0 my( $pack, $file, $line ) = caller( $frame );
440 0         0 $err =~ s/\n$//gs;
441 0         0 $self->{ 'error' } = ${ $class . '::ERROR' } = $err;
  0         0  
442 0         0 return( undef() );
443             }
444 0   0     0 return( $self->{ 'error' } || $ERROR );
445             }
446              
447             *extension = \&ext;
448              
449             sub ext
450             {
451 33     33 0 349 my $self = shift( @_ );
452 33 100       62 if( @_ )
453             {
454 11         21 my $val = shift( @_ );
455 11 50 33     109 if( length( $val ) && $val !~ /^[$PHONEDIGIT]+$/ )
456             {
457 0         0 warn( "'$val' is not a valid extension ([$PHONEDIGIT]+)\n" );
458 0         0 return( undef() );
459             }
460 11         24 delete( $self->{ 'cache' } );
461 11         16 $self->{ 'ext' } = $val;
462             }
463 33         101 return( $self->{ 'ext' } );
464             }
465              
466             sub international_code
467             {
468 276     276 1 338 my $self = shift( @_ );
469 276 100       517 if( @_ )
470             {
471 10         19 my $val = shift( @_ );
472 10 50 33     72 if( length( $val ) && $val !~ /^[$PHONEDIGIT]+$/ )
473             {
474 0         0 warn( "'$val' is not a valid international code.\n" );
475 0         0 return( undef() );
476             }
477 10         20 delete( $self->{ 'cache' } );
478             ## The international code was provided by the user as opposed to using the context() method,
479             ## so we flag it properly so it can be used in stringification of the phone number
480 10         15 $self->{ '_intl_code_from_context' } = 0;
481 10         12 $self->{ 'is_global' } = 1;
482 10         17 $self->{ 'intl_code' } = $val;
483             }
484 276         608 return( $self->{ 'intl_code' } );
485             }
486              
487             sub is_global
488             {
489 21     21 1 9644 return( shift->{ 'is_global' } );
490             }
491              
492             sub is_local
493             {
494 0     0 1 0 return( shift->{ 'is_local' } );
495             }
496              
497             sub is_other
498             {
499 0     0 1 0 return( shift->{ 'is_other' } );
500             }
501              
502             sub is_vanity
503             {
504 2     2 1 6 return( shift->{ 'is_vanity' } );
505             }
506              
507             *isdn_subaddress = \&isub;
508             sub isub
509             {
510 21     21 1 39 my $self = shift( @_ );
511 21 50       45 if( @_ )
512             {
513 0         0 my $val = shift( @_ );
514 0 0 0     0 if( length( $val ) && $val !~ /^$URIC$/ )
515             {
516 0         0 warn( "'$val' is not a isdn subaddress\n" );
517 0         0 return( undef() );
518             }
519 0         0 delete( $self->{ 'cache' } );
520 0         0 $self->{ 'isub' } = $val;
521             }
522 21         97 return( $self->{ 'isub' } );
523             }
524              
525             sub original
526             {
527 0     0 1 0 return( shift->{ 'original' } );
528             }
529              
530             sub prepend_context
531             {
532 1     1 0 279 my $self = shift( @_ );
533 1 50       4 if( @_ )
534             {
535 1         3 $self->{ '_prepend_context' } = shift( @_ );
536 1         2 delete( $self->{ 'cache' } );
537             }
538 1         2 return( $self->{ '_prepend_context' } );
539             }
540              
541             sub private
542             {
543 3     3 1 4 my $self = shift( @_ );
544 3         7 my( $name, $val ) = @_;
545 3 50       7 if( length( $name ) )
546             {
547             ## The value could be blank and if so, we would remove the parameter
548 0 0       0 if( defined( $val ) )
549             {
550 0 0       0 if( $name !~ /^$PNAME$/ )
551             {
552 0         0 warn( "'$name' is not a valid parameter name.\n" );
553 0         0 return( undef() );
554             }
555 0 0       0 if( length( $val ) )
556             {
557 0 0       0 if( $val !~ /^$PVALUE$/ )
558             {
559 0         0 warn( "'$val' is not a valid parameter value.\n" );
560 0         0 return( undef() );
561             }
562 0         0 delete( $self->{ 'cache' } );
563 0         0 $self->{ 'private' }->{ $name } = $val;
564 0         0 return( $self->{ 'private' }->{ $name } );
565             }
566             else
567             {
568 0         0 return( delete( $self->{ 'private' }->{ $name } ) );
569             }
570             }
571             else
572             {
573 0 0       0 return( wantarray() ? %${$self->{ 'private' }->{ $name }} : \%${$self->{ 'private' }->{ $name }} );
  0         0  
  0         0  
574             }
575             }
576 3 50       6 return( wantarray() ? %{$self->{ 'private' }} : \%{$self->{ 'private' }} );
  0         0  
  3         9  
577             }
578              
579             sub subscriber
580             {
581 21     21 1 37 my $self = shift( @_ );
582 21 50       46 if( @_ )
583             {
584 0         0 my $val = shift( @_ );
585 0 0 0     0 if( length( $val ) && $val !~ /^$TEL_SUBSCRIBER$/ )
586             {
587 0         0 warn( "'$val' is not a valid subscriber value.\n" );
588 0         0 return( undef() );
589             }
590 0         0 delete( $self->{ 'cache' } );
591 0         0 $self->{ 'subscriber' } = $val;
592             }
593 21         85 return( $self->{ 'subscriber' } );
594             }
595              
596             sub type
597             {
598 0     0 1 0 return( shift->{ 'type' } );
599             }
600              
601             sub _extract_intl_code
602             {
603 271     271   412 my $self = shift( @_ );
604 271   50     572 my $str = shift( @_ ) || return( '' );
605             #print( STDERR "Trying to find international code from '$str'\n" );
606 271         350 my $code;
607             ## Extract the global idd
608 271         691 $self->_load_countries;
609 271         1382 ( my $str2 = $str ) =~ s/[\Q$VISUAL_SEPARATOR\E]+//g;
610 271         612 $str2 =~ s/^tel\://;
611 271 100       1963 if( $str2 =~ /^\+($IDD_RE)/ )
612             {
613 268         783 my $idd = $1;
614             #print( STDERR "\tIDD is '$idd'\n" );
615 268 50       674 if( CORE::exists( $COUNTRIES->{ $idd } ) )
616             {
617 268         615 my $idds = $COUNTRIES->{ $idd }->[0]->{ 'idd' };
618             #print( STDERR "\tFound entry.\n" );
619 268         517 foreach my $thisIdd ( @$idds )
620             {
621 277         365 my $check = $thisIdd;
622 277         553 $check =~ s/\D//g;
623 277 100       578 if( $check eq $idd )
624             {
625             #$temp->{ 'context' } = '+' . $thisIdd;
626 268         342 $code = $thisIdd;
627 268         527 last;
628             }
629             }
630             }
631             }
632             #print( STDERR "\tReturning '$code'\n" );
633 271         981 return( $code );
634             }
635              
636             sub _extract_local_number
637             {
638 262     262   383 my $self = shift( @_ );
639 262         451 my( $intl_code, $subscriber ) = @_;
640 262         361 my $j = 0;
641 262         572 for( my $i = 0; $i < length( $intl_code ); $i++ )
642             {
643             # Skip until we are on a number
644 800 100       2069 next if( substr( $intl_code, $i, 1 ) !~ /^\d$/ );
645             # and here too so we can compare number to number
646 758         1730 while( substr( $subscriber, $j, 1 ) !~ /^\d$/ )
647             {
648 262         643 $j++;
649             }
650             # Our international code does not seem to match the prefix of our subscriber! This should not happen.
651 758 50       1426 if( substr( $subscriber, $j, 1 ) ne substr( $intl_code, $i, 1 ) )
652             {
653             # printf( STDERR "Mismatch... %s at position %d vs %s at position %d\n", substr( $intl_code, $i, 1 ), $i, substr( $subscriber, $j, 1 ), $j );
654             #last;
655 0         0 return( '' );
656             }
657 758         1306 $j++;
658             }
659 262         856 $j++ while( substr( $subscriber, $j, 1 ) !~ /^\d$/ );
660 262         880 return( substr( $subscriber, $j ) );
661             }
662              
663             # Check if two objects are the same object
664             # https://tools.ietf.org/search/rfc3966#section-4
665             sub _obj_eq
666             {
667             ##return overload::StrVal( $_[0] ) eq overload::StrVal( $_[1] );
668 3     3   24 no overloading;
  3         7  
  3         701  
669 0     0   0 my $self = shift( @_ );
670 0         0 my $other = shift( @_ );
671 0 0 0     0 return( 0 ) if( !ref( $other ) || !$other->isa( 'URI::tel' ) );
672 0         0 my $sub = $self->canonical->subscriber;
673 0         0 my $sub2 = $other->canonical->subscriber;
674 0         0 $sub =~ s/^\+//;
675 0         0 $sub2 =~ s/^\+//;
676 0 0       0 return( 0 ) if( $sub ne $sub2 );
677 0         0 my $context = $self->context;
678 0         0 my $context2 = $other->context;
679 0 0       0 return( 0 ) if( $context ne $context2 );
680 0         0 my $ext = $self->ext;
681 0         0 my $ext2 = $other->ext;
682 0 0       0 return( 0 ) if( $ext ne $ext2 );
683 0         0 my $priv = $self->private;
684 0         0 my $priv2 = $other->private;
685 0         0 foreach my $k ( keys( %$priv ) )
686             {
687 0 0       0 return( 0 ) if( !exists( $priv2->{ $k } ) );
688 0 0       0 return( 0 ) if( $priv->{ $k } ne $priv2->{ $k } );
689             }
690 0         0 foreach my $k ( keys( %$priv2 ) )
691             {
692 0 0       0 return( 0 ) if( !exists( $priv->{ $k } ) );
693 0 0       0 return( 0 ) if( $priv2->{ $k } ne $priv->{ $k } );
694             }
695 3     3   22 use overloading;
  3         5  
  3         1654  
696 0         0 return( 1 );
697             }
698              
699             ## Taken from http://www.perlmonks.org/bare/?node_id=319761
700             ## This will do a split on a semi-colon, but being mindful if before it there is an escaped backslash
701             ## For example, this would not be skipped: something\;here
702             ## But this would be split: something\\;here resulting in something\ and here after unescaping
703             sub split_str
704             {
705 10     10 0 13 my $self = shift( @_ );
706 10         16 my $s = shift( @_ );
707 10 50       20 my $sep = @_ ? shift( @_ ) : ';';
708 10         13 my @parts = ();
709 10         12 my $i = 0;
710 10         76 foreach( split( /(\\.)|$sep/, $s ) )
711             {
712 22 100       33 defined( $_ ) ? do{ $parts[$i] .= $_ } : do{ $i++ };
  16         33  
  6         9  
713             }
714 10         33 return( @parts );
715             }
716              
717             sub _load_countries
718             {
719 539     539   904 my $self = shift( @_ );
720 539 100       1462 if( !%$COUNTRIES )
721             {
722 2         18 my $in = 0;
723 2         5 my $hash = {};
724 2         467 my @data = ;
725 2         13 foreach ( @data )
726             {
727 960         1179 chomp;
728 960 100 100     1886 next unless( $in || /^\#{2} BEGIN DATA/ );
729 504 100       793 last if( /^\#{2} END DATA/ );
730 502         510 $in++;
731 502 100       750 next if( /^\#{2} BEGIN DATA/ );
732 500         2231 my( $cc, $cc3, $name, $idd ) = split( /[[:blank:]]*\;[[:blank:]]*/, $_ );
733 500 100       1204 my $keys = index( $idd, ',' ) != -1 ? [ split( /[[:blank:]]*\,[[:blank:]]*/, $idd ) ] : [ $idd ];
734 500         1519 my $info =
735             {
736             'cc' => $cc,
737             'cc3' => $cc3,
738             'name' => $name,
739             'idd' => $idd,
740             };
741 500         763 $info->{ 'idd' } = $keys;
742 500         672 foreach my $k ( @$keys )
743             {
744 514         599 my $k2 = $k;
745 514         728 $k2 =~ s/-//gs;
746 514 100       1336 $hash->{ $k2 } = [] if( !exists( $hash->{ $k2 } ) );
747 514         603 push( @{$hash->{ $k2 }}, $info );
  514         1189  
748             }
749             }
750 2         6 $COUNTRIES = $hash;
751 2         109 my @list = sort{ $b <=> $a } keys( %$hash );
  3278         3438  
752 2         50 my $re_list = join( '|', @list );
753 2         957 $IDD_RE = qr{(?:$re_list)};
754             }
755             }
756              
757             1;
758              
759             __DATA__