File Coverage

blib/lib/URI/tel.pm
Criterion Covered Total %
statement 213 306 69.6
branch 111 166 66.8
condition 18 42 42.8
subroutine 22 32 68.7
pod 16 21 76.1
total 380 567 67.0


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## tel.pm
3             ## Version 0.7
4             ## Copyright(c) 2016-2019 Jacques Deguest
5             ## Author: Jacques Deguest
6             ## Created 2016/02/12
7             ## Modified 2019/08/26
8             ## All rights reserved.
9             ##
10             ## This program is free software; you can redistribute it and/or modify it
11             ## under the same terms as Perl itself.
12             ##----------------------------------------------------------------------------
13             package URI::tel;
14             BEGIN
15 0         0 {
16 3     3   67989 use strict;
  3         19  
  3         96  
17 3     3   1284 use parent 'URI';
  3         1017  
  3         15  
18 3     3   264 our( $VERSION, $VERBOSE, $DEBUG, $ERROR );
19 3         5 our( $RESERVED, $MARK, $UNRESERVED, $PCT_ENCODED, $URIC, $ALPHA, $DIGIT, $ALPHANUM, $HEXDIG );
20 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 );
21 3         4 our( $COUNTRIES, $IDD_RE );
22 3         10269 $VERSION = '0.7';
23             use overload ('""' => 'as_string',
24 0     0   0 '==' => sub { _obj_eq(@_) },
25 0     0   0 '!=' => sub { !_obj_eq(@_) },
26 3         35 fallback => 1,
27 3     3   26224 );
  3         7  
28             };
29              
30             {
31             #$RESERVED = qr{[\[\;\/\?\:\@\&\=\+\$\,\[\]]+};
32             $RESERVED = q{[;/?:@&=+$,[]+};
33             $MARK = q{-_.!~*'()}; #'; emacs
34             $UNRESERVED = qq{A-Za-z0-9\Q$MARK\E};
35             ## "%" HEXDIG HEXDIG
36             $PCT_ENCODED = qr{\%[0-9A-Fa-f]{2}};
37             #$URIC = quotemeta( $RESERVED ) . $UNRESERVED . "%";
38             $URIC = qr{(?:[\Q$RESERVED\E]+|[$UNRESERVED]+|(?:$PCT_ENCODED)+)};
39             # $ALPHA = qr{A-Za-z};
40             # $DIGIT = qr{\d};
41             $ALPHA = qr{A-Za-z};
42             $DIGIT = qq{0-9};
43             # $ALPHANUM = qr{A-Za-z\d};
44             $ALPHANUM = qq{A-Za-z0-9};
45             # $HEXDIG = qr{\dA-F};
46             $HEXDIG = qr{\dA-F};
47             # $PARAM_UNRESERVED = qr{[\[\]\/\:\&\+\$]+};
48             $PARAM_UNRESERVED = q{[]/:&+$};
49             $VISUAL_SEPARATOR = q(-.());
50             ## DIGIT / [ visual-separator ]
51             # $PHONEDIGIT = qr{$DIGIT\Q$VISUAL_SEPARATOR\E};
52             $PHONEDIGIT = qq{$DIGIT\Q$VISUAL_SEPARATOR\E};
53             ## "+" *phonedigit DIGIT *phonedigit
54             $GLOBAL_NUMBER_DIGITS = qr{\+[$PHONEDIGIT]*[$DIGIT]+[$PHONEDIGIT]*};
55             ## param-unreserved / unreserved / pct-encoded
56             $PARAMCHAR = qr{(?:[\Q$PARAM_UNRESERVED\E]+|[$UNRESERVED]+|(?:$PCT_ENCODED)+)};
57             ## alphanum / alphanum *( alphanum / "-" ) alphanum
58             $DOMAINLABEL = qr{(?:[$ALPHANUM]+|[$ALPHANUM]+(?:[$ALPHANUM\-]+)*[$ALPHANUM]+)};
59             ## ALPHA / ALPHA *( alphanum / "-" ) alphanum
60             $TOPLABEL = qr{(?:[$ALPHA]+|[$ALPHA]+[$ALPHANUM\-]*[$ALPHANUM]+)};
61             ## *( domainlabel "." ) toplabel [ "." ]
62             $DOMAINNAME = qr{(?:$DOMAINLABEL\.)*(?:$TOPLABEL\.?)+};
63             ## domainname / global-number-digits
64             $DESCRIPTOR = qr{(?:$DOMAINNAME|$GLOBAL_NUMBER_DIGITS)};
65             ## 1*( alphanum / "-" )
66             $PNAME = qr{[$ALPHANUM\-]+};
67             ## 1*paramchar
68             $PVALUE = qr{(?:$PARAMCHAR)};
69             ## ";" pname ["=" pvalue ]
70             $PARAMETER = qr{\;$PNAME=$PVALUE};
71             ## ";ext=" 1*phonedigit
72             ##$EXTENSION = qr{\;ext=[$PHONEDIGIT]+}msxi;
73             ## Tweaking the rfc regular expression to add often used extension format
74             ## See: https://discussions.apple.com/thread/1635858?start=0&tstart=0
75             ## or
76             ## http://stackoverflow.com/questions/2403767/string-format-phone-numbers-with-extension
77             $EXTENSION = qr{(?:[\;\,]?ext[\=\.]?|x)[$PHONEDIGIT]+}msxi;
78             ## ";isub=" 1*uric
79             $ISDN_SUBADDRESS = qr{\;isub=$URIC}msxi;
80             ## ";phone-context=" descriptor
81             $CONTEXT = qr{\;phone-context=$DESCRIPTOR}msxi;
82             ## parameter / extension / isdn-subaddress
83             $PAR = qr{(?:(?:($PARAMETER)|($EXTENSION)|($ISDN_SUBADDRESS)))+};
84             ## HEXDIG / "*" / "#" / [ visual-separator ]
85             $PHONEDIGIT_HEX = qr{[$HEXDIG\*\#$VISUAL_SEPARATOR]+};
86             ## *phonedigit-hex (HEXDIG / "*" / "#")*phonedigit-hex
87             $LOCAL_NUMBER_DIGITS = qr{(?:$PHONEDIGIT_HEX)?[$HEXDIG\*\#]+(?:$PHONEDIGIT_HEX)?};
88             ## global-number-digits *par => "+" *phonedigit DIGIT *phonedigit
89             $GLOBAL_NUMBER = qr{($GLOBAL_NUMBER_DIGITS)($PAR)*};
90             ## local-number-digits *par context *par
91             $LOCAL_NUMBER = qr{($LOCAL_NUMBER_DIGITS)($PAR)*($CONTEXT)($PAR)*};
92             ## This is a non-rfc standard requirement, but a necessity to catch local number with no context
93             ## such as 03-1234-5678 plain and simple
94             $OTHER = qr{(\+?[$PHONEDIGIT]*[$DIGIT]+[$PHONEDIGIT]*)($PAR)*};
95             ## Like +1-800-LAWYR-UP => +1-800-52997-87
96             $VANITY = qr{(\+?[$PHONEDIGIT]*[A-Z0-9\Q$VISUAL_SEPARATOR\E]+[$PHONEDIGIT]*)};
97             $TEL_SUBSCRIBER = qr{(?:$VANITY|$GLOBAL_NUMBER|$LOCAL_NUMBER|$OTHER)}xs;
98             ##$TEL_SUBSCRIBER = qr{(?:$GLOBAL_NUMBER)};
99             $TEL_URI = qr{(?:tel\:)?$TEL_SUBSCRIBER};
100             # https://tools.ietf.org/search/rfc3966#section-3
101            
102             $COUNTRIES = {};
103             }
104              
105             sub new
106             {
107 311     311 1 149855 my $this = shift( @_ );
108 311         482 my $str = shift( @_ );
109 311   66     1061 my $class = ref( $this ) || $this;
110 311         401 my $orig = $str;
111 311         727 $str =~ s/[[:blank:]]+//gs;
112 311         487 my $temp = {};
113 311         504 my @matches = ();
114 311         392 my @names = ();
115 311 100       5423 if( @matches = $str =~ /^((?:tel\:)?$GLOBAL_NUMBER)$/ )
    100          
    100          
    100          
116             {
117 270         620 @names = qw( all subscriber params last_param );
118 270         566 $temp->{ 'type' } = 'global';
119             }
120             elsif( @matches = $str =~ /^((?:tel\:)?$LOCAL_NUMBER)$/ )
121             {
122 5         17 $temp->{ 'type' } = 'local';
123 5         18 @names = qw( all subscriber params1 last_param1 ignore5 ignore6 context params2 last_param2 ignore10 ignore11 );
124 5 50       15 $temp->{ '_has_context_param' } = 1 if( length( $matches[6] ) );
125             }
126             elsif( @matches = $str =~ /^((?:tel\:)?$OTHER)$/ )
127             {
128 31         87 $temp->{ 'type' } = 'other';
129 31         71 @names = qw( all subscriber params ignore4 last_param );
130             }
131             elsif( @matches = $str =~ /^((?:tel\:)?$VANITY)$/ )
132             {
133 2         8 $temp->{ 'type' } = 'vanity';
134 2         4 @names = qw( all subscriber );
135             }
136             else
137             {
138 3         13 $ERROR = "Unknown telephone number '$str'.";
139 3         91 warn( $ERROR );
140             }
141            
142 311 100       968 if( $str =~ /^(?:tel\:)?\+/ )
143             {
144             ## Extract the global idd
145 271         707 $this->_load_countries;
146 271         1306 ( my $str2 = $str ) =~ s/[\Q$VISUAL_SEPARATOR\E]+//g;
147 271         601 $str2 =~ s/^tel\://;
148 271 50       1878 if( $str2 =~ /^\+($IDD_RE)/ )
149             {
150 271         688 my $idd = $1;
151 271 50       693 if( CORE::exists( $COUNTRIES->{ $idd } ) )
152             {
153 271         596 my $idds = $COUNTRIES->{ $idd }->[0]->{ 'idd' };
154 271         490 foreach my $thisIdd ( @$idds )
155             {
156 280         413 my $check = $thisIdd;
157 280         492 $check =~ s/\D//g;
158 280 100       568 if( $check eq $idd )
159             {
160 271         649 $temp->{ 'context' } = '+' . $thisIdd;
161 271         844 last;
162             }
163             }
164             }
165             }
166             }
167            
168             ## The component name for each match
169 311         1132 @$temp{ @names } = @matches;
170 311 100       926 $temp->{ 'params' } = $temp->{ 'params1' } ? $temp->{ 'params1' } : $temp->{ 'params2' } if( !length( $temp->{ 'params' } ) );
    100          
171 311         601 $temp->{ 'context' } =~ s/;[^=]+=(.*?)$/$1/gs;
172            
173             my $hash = {
174             'original' => ( $orig ne $str ) ? $orig : $temp->{ 'all' },
175             'is_global' => $temp->{ 'type' } eq 'global' ? 1 : 0,
176             'is_local' => ( $temp->{ 'type' } eq 'local' or $temp->{ 'type' } eq 'other' ) ? 1 : 0,
177             'is_other' => $temp->{ 'type' } eq 'other' ? 1 : 0,
178             'is_vanity' => $temp->{ 'type' } eq 'vanity' ? 1 : 0,
179             'subscriber' => $temp->{ 'subscriber' },
180             'params' => $temp->{ 'params' },
181             'last_param' => $temp->{ 'last_param' } ? $temp->{ 'last_param' } : $temp->{ 'last_param1' } ? $temp->{ 'last_param1' } : $temp->{ 'last_param2' },
182 311 100 100     3208 'context' => $temp->{ 'context' },
    100          
    100          
    100          
    100          
    100          
    100          
183             };
184 311         600 my $self = bless( $hash, $class );
185 311         503 my $prams = [];
186 311 100       582 if( length( $temp->{ 'params' } ) )
187             {
188 10         19 my $pram_str = $temp->{ 'params' };
189 10         33 $pram_str =~ s/^[\.\,\#\;]//;
190 10         30 $prams = [ $self->split_str( $pram_str ) ];
191             }
192             ## Private parameters
193 311         428 my $priv = {};
194 311         575 foreach my $this ( @$prams )
195             {
196 16         73 $this =~ s/^(x|ext)\.?(\d+)$/ext=$2/i;
197 16         48 my( $p, $v ) = split( /=/, $this, 2 );
198 16         33 $p =~ s/\-/\_/gs;
199 16 100       65 if( lc( $p ) =~ /^(ext|isdn_subaddress)$/ )
    100          
200             {
201 10         29 $hash->{ lc( $p ) } = $v;
202             }
203             elsif( lc( $p ) eq 'phone_context' )
204             {
205 2         4 $hash->{ 'context' } = $v;
206 2         5 $temp->{ '_has_context_param' } = 1;
207             }
208             else
209             {
210 4         13 $priv->{ lc( $p ) } = $v;
211             }
212             }
213 311         652 $self->{ 'private' } = $priv;
214 311 100 100     1116 $self->{ 'ext' } = $temp->{ 'ext' } if( !length( $hash->{ 'ext' } ) && !$self->{ 'is_vanity' } );
215 311         528 $self->{ 'ext' } =~ s/\D//gs;
216 311 100       560 $self->{ '_prepend_context' } = $temp->{ '_has_context_param' } ? 0 : 1;
217 311         1343 return( $self );
218             }
219              
220             sub as_string
221             {
222 48     48 1 17574 my $self = shift( @_ );
223 48 100       148 return( $self->{ 'cache' } ) if( length( $self->{ 'cache' } ) );
224 47 100       167 my @uri = ( 'tel:' . $self->{ 'subscriber' } ) if( length( $self->{ 'subscriber' } ) );
225 47         62 my @params = ();
226 47 100       135 push( @params, sprintf( "ext=%s", $self->{ 'ext' } ) ) if( length( $self->{ 'ext' } ) );
227 47 50       86 push( @params, sprintf( "isub=%s", $self->{ 'isdn_subaddress' } ) ) if( length( $self->{ 'isdn_subaddress' } ) );
228 47 100       80 if( length( $self->{ 'context' } ) )
229             {
230 31 100 66     138 if( !$self->{ '_prepend_context' } )
    100          
231             {
232 15         46 push( @params, sprintf( "phone-context=%s", $self->{ 'context' } ) );
233             }
234             elsif( $self->{ 'subscriber' } !~ /^\+\d+/ && $self->{ 'context' } !~ /[a-zA-Z]+/ )
235             {
236 1         15 @uri = ( 'tel:' . $self->{ 'context' } . '.' . $self->{ 'subscriber' } );
237             }
238             }
239 47         79 my $priv = $self->{ 'private' };
240 47         142 foreach my $k ( sort( keys( %$priv ) ) )
241             {
242 8 50       33 push( @params, sprintf( "$k=%s", $priv->{ $k } ) ) if( length( $priv->{ $k } ) );
243             }
244 47 100       157 push( @uri, join( ';', @params ) ) if( scalar( @params ) );
245 47         126 $self->{ 'cache' } = join( ';', @uri );
246 47         424 return( $self->{ 'cache' } );
247             }
248              
249             *letters2digits = \&aton;
250              
251             sub aton
252             {
253 42     42 1 64 my $self = shift( @_ );
254 42   66     159 my $str = shift( @_ ) || $self->{ 'subscriber' };
255 42         64 my $letters = 'abcdefghijklmnopqrstuvwxyz';
256 42         55 my $digits = '22233344455566677778889999';
257 42 100 66     200 return( $str ) if( $str !~ /[a-zA-Z]+/ || !$self->is_vanity );
258 4         9 $str = lc( $str );
259 4         9 my $res = '';
260 4         9 for( my $i = 0; $i < length( $str ); $i++ )
261             {
262 52         72 my $c = substr( $str, $i, 1 );
263 52         65 my $p = index( $letters, $c );
264 52 100       109 $res .= $p != -1 ? substr( $digits, $p, 1 ) : $c;
265             }
266 4         10 return( $res );
267             }
268              
269             sub canonical
270             {
271 42     42 1 1617 my $self = shift( @_ );
272 42         73 my $tel = $self->aton;
273 42         283 $tel =~ s/[\Q$VISUAL_SEPARATOR\E]+//gs;
274 42         127 my $uri = $self->new( "tel:$tel" );
275 42 100       130 $uri->ext( $self->{ 'ext' } ) if( length( $self->{ 'ext' } ) );
276 42 50       89 $uri->isub( $self->{ 'isdn_subaddress' } ) if( length( $self->{ 'isdn_subaddress' } ) );
277 42 100       106 $uri->context( $self->{ 'context' } ) if( length( $self->{ 'context' } ) );
278 42         70 $uri->{_has_context_param} = $self->{_has_context_param};
279 42         58 $uri->{_prepend_context} = $self->{_prepend_context};
280 42         77 my $priv = $self->{ 'private' };
281 42         89 %{$uri->{ 'private' }} = %$priv;
  42         80  
282 42         128 return( $uri );
283             }
284              
285             sub cc2context
286             {
287 1     1 0 7 my $self = shift( @_ );
288 1         5 my $cc = uc( shift( @_ ) );
289 1 50       4 return( $self->error( "No country code provided." ) ) if( !length( $cc ) );
290 1         5 $self->_load_countries;
291 1         2 my $hash = $COUNTRIES;
292 1         107 foreach my $k ( sort( keys( %$hash ) ) )
293             {
294             ## array ref
295 205         244 my $ref = $hash->{ $k };
296 205         315 foreach my $this ( @$ref )
297             {
298 217 100       502 if( $this->{ 'cc' } eq $cc )
299             {
300 1         8 return( '+' . $k );
301             }
302             }
303             }
304             ## Nothing found
305 0         0 return( '' );
306             }
307              
308             sub clone
309             {
310 0     0 1 0 my $self = shift( @_ );
311 0         0 my $class = ref( $self );
312 0         0 my $hash = {};
313 0         0 my @keys = keys( %$self );
314 0         0 @$hash{ @keys } = @$self{ @keys };
315 0         0 delete( $hash->{ 'cache' } );
316 0         0 return( bless( $hash, $class ) );
317             }
318              
319             sub context
320             {
321 293     293 1 1556 my $self = shift( @_ );
322 293 100       600 if( @_ )
323             {
324 27         41 my $str = shift( @_ );
325 27 50       418 if( $str !~ /^$DESCRIPTOR$/ )
326             {
327 0         0 warn( "'$str' is not a valid context\n" );
328 0         0 return( undef() );
329             }
330 27         54 delete( $self->{ 'cache' } );
331 27         48 $self->{ '_has_context_param' } = 1;
332 27         32 $self->{ '_prepend_context' } = 0;
333 27         51 $self->{ 'context' } = $str;
334             }
335 293         677 return( $self->{ 'context' } );
336             }
337              
338             sub country
339             {
340 21     21 1 39 my $self = shift( @_ );
341 21         53 $self->_load_countries;
342 21         31 my $hash = $COUNTRIES;
343 21 100       88 my $idd = substr( $self->{ 'subscriber' }, 0, 1 ) eq '+' ? $self->{ 'subscriber' } : substr( $self->{ 'context' }, 0, 1 ) eq '+' ? $self->{ 'context' } : '';
    100          
344             ## Something like +33(0)3-45-67-89-12 or +33-345-67-89-12
345             ## Make sure we got a phone number without any visual separator
346 21         43 my $uri = $self->canonical;
347 21 100       74 $idd = substr( $uri->{ 'subscriber' }, 0, 1 ) eq '+' ? $uri->{ 'subscriber' } : substr( $uri->{ 'context' }, 0, 1 ) eq '+' ? $uri->{ 'context' } : '';
    100          
348             ## Remove the '+'
349 21         35 $idd = substr( $idd, 1 );
350 21         601 foreach my $k ( %$hash )
351             {
352 8208 100       14284 next if( length( $k ) > length( $idd ) );
353             ## We found a match
354             ## We return all the countries that match the international prefix
355 1271 100       2176 if( substr( $idd, 0, length( $k ) ) eq $k )
356             {
357 10         14 my $ref = $hash->{ $k };
358 10 50       106 return( wantarray() ? @$ref : \@$ref );
359             }
360             }
361             ## We got here, nothing found
362 11 50       146 return( wantarray() ? () : [] );
363             }
364              
365             sub error
366             {
367 0     0 0 0 my $self = shift( @_ );
368 0         0 my $level = 0;
369 0         0 my $caller = caller;
370 0         0 my $err = join( '', @_ );
371 0         0 my $hash = $self->_obj2h;
372 0   0     0 my $class = ref( $self ) || $self;
373 0 0 0     0 if( $err && length( $err ) )
374             {
375 0         0 my( $frame, $caller ) = ( $level, '' );
376 0         0 while( $caller = ( caller( $frame ) )[ 0 ] )
377             {
378 0 0       0 last if( $caller ne 'URI::tel' );
379 0         0 $frame++;
380             }
381 0         0 my( $pack, $file, $line ) = caller( $frame );
382 0         0 $err =~ s/\n$//gs;
383 0         0 $hash->{ 'error' } = ${ $class . '::ERROR' } = $err;
  0         0  
384 0         0 return( undef() );
385             }
386 0   0     0 return( $hash->{ 'error' } || $ERROR );
387             }
388              
389             *extension = \&ext;
390              
391             sub ext
392             {
393 43     43 0 403 my $self = shift( @_ );
394 43 100       91 if( @_ )
395             {
396 21         39 my $val = shift( @_ );
397 21 50 33     176 if( length( $val ) && $val !~ /^[$PHONEDIGIT]+$/ )
398             {
399 0         0 warn( "'$val' is not a valid extension ([$PHONEDIGIT]+)\n" );
400 0         0 return( undef() );
401             }
402 21         43 delete( $self->{ 'cache' } );
403 21         41 $self->{ 'ext' } = $val;
404             }
405 43         133 return( $self->{ 'ext' } );
406             }
407              
408             sub is_global
409             {
410 21     21 1 11388 return( shift->{ 'is_global' } );
411             }
412              
413             sub is_local
414             {
415 0     0 1 0 return( shift->{ 'is_global' } );
416             }
417              
418             sub is_other
419             {
420 0     0 1 0 return( shift->{ 'is_other' } );
421             }
422              
423             sub is_vanity
424             {
425 4     4 1 15 return( shift->{ 'is_vanity' } );
426             }
427              
428             *isdn_subaddress = \&isub;
429             sub isub
430             {
431 21     21 1 45 my $self = shift( @_ );
432 21 50       47 if( @_ )
433             {
434 0         0 my $val = shift( @_ );
435 0 0 0     0 if( length( $val ) && $val !~ /^$URIC$/ )
436             {
437 0         0 warn( "'$val' is not a isdn subaddress\n" );
438 0         0 return( undef() );
439             }
440 0         0 delete( $self->{ 'cache' } );
441 0         0 $self->{ 'isub' } = $val;
442             }
443 21         112 return( $self->{ 'isub' } );
444             }
445              
446             sub original
447             {
448 0     0 1 0 return( shift->{ 'original' } );
449             }
450              
451             sub prepend_context
452             {
453 1     1 0 311 my $self = shift( @_ );
454 1 50       7 if( @_ )
455             {
456 1         5 $self->{ '_prepend_context' } = shift( @_ );
457 1         2 delete( $self->{ 'cache' } );
458             }
459 1         3 return( $self->{ '_prepend_context' } );
460             }
461              
462             sub private
463             {
464 3     3 1 7 my $self = shift( @_ );
465 3         6 my( $name, $val ) = @_;
466 3 50       8 if( length( $name ) )
467             {
468             ## The value could be blank and if so, we would remove the parameter
469 0 0       0 if( defined( $val ) )
470             {
471 0 0       0 if( $name !~ /^$PNAME$/ )
472             {
473 0         0 warn( "'$name' is not a valid parameter name.\n" );
474 0         0 return( undef() );
475             }
476 0 0       0 if( length( $val ) )
477             {
478 0 0       0 if( $val !~ /^$PVALUE$/ )
479             {
480 0         0 warn( "'$val' is not a valid parameter value.\n" );
481 0         0 return( undef() );
482             }
483 0         0 delete( $self->{ 'cache' } );
484 0         0 $self->{ 'private' }->{ $name } = $val;
485 0         0 return( $self->{ 'private' }->{ $name } );
486             }
487             else
488             {
489 0         0 return( delete( $self->{ 'private' }->{ $name } ) );
490             }
491             }
492             else
493             {
494 0 0       0 return( wantarray() ? %${$self->{ 'private' }->{ $name }} : \%${$self->{ 'private' }->{ $name }} );
  0         0  
  0         0  
495             }
496             }
497 3 50       9 return( wantarray() ? %{$self->{ 'private' }} : \%{$self->{ 'private' }} );
  0         0  
  3         9  
498             }
499              
500             sub subscriber
501             {
502 21     21 1 45 my $self = shift( @_ );
503 21 50       55 if( @_ )
504             {
505 0         0 my $val = shift( @_ );
506 0 0 0     0 if( length( $val ) && $val !~ /^$TEL_SUBSCRIBER$/ )
507             {
508 0         0 warn( "'$val' is not a valid subscriber value.\n" );
509 0         0 return( undef() );
510             }
511 0         0 delete( $self->{ 'cache' } );
512 0         0 $self->{ 'subscriber' } = $val;
513             }
514 21         99 return( $self->{ 'subscriber' } );
515             }
516              
517             sub type
518             {
519 0     0 1 0 return( shift->{ 'type' } );
520             }
521              
522             # Check if two objects are the same object
523             # https://tools.ietf.org/search/rfc3966#section-4
524             sub _obj_eq
525             {
526             ##return overload::StrVal( $_[0] ) eq overload::StrVal( $_[1] );
527 3     3   28 no overloading;
  3         7  
  3         714  
528 0     0   0 my $self = shift( @_ );
529 0         0 my $other = shift( @_ );
530 0 0 0     0 return( 0 ) if( !ref( $other ) || !$other->isa( 'URI::tel' ) );
531 0         0 my $sub = $self->canonical->subscriber;
532 0         0 my $sub2 = $other->canonical->subscriber;
533 0         0 $sub =~ s/^\+//;
534 0         0 $sub2 =~ s/^\+//;
535 0 0       0 return( 0 ) if( $sub ne $sub2 );
536 0         0 my $context = $self->context;
537 0         0 my $context2 = $other->context;
538 0 0       0 return( 0 ) if( $context ne $context2 );
539 0         0 my $ext = $self->ext;
540 0         0 my $ext2 = $other->ext;
541 0 0       0 return( 0 ) if( $ext ne $ext2 );
542 0         0 my $priv = $self->private;
543 0         0 my $priv2 = $other->private;
544 0         0 foreach my $k ( keys( %$priv ) )
545             {
546 0 0       0 return( 0 ) if( !exists( $priv2->{ $k } ) );
547 0 0       0 return( 0 ) if( $priv->{ $k } ne $priv2->{ $k } );
548             }
549 0         0 foreach my $k ( keys( %$priv2 ) )
550             {
551 0 0       0 return( 0 ) if( !exists( $priv->{ $k } ) );
552 0 0       0 return( 0 ) if( $priv2->{ $k } ne $priv->{ $k } );
553             }
554 3     3   22 use overloading;
  3         7  
  3         2014  
555 0         0 return( 1 );
556             }
557              
558             ## Taken from http://www.perlmonks.org/bare/?node_id=319761
559             ## This will do a split on a semi-colon, but being mindful if before it there is an escaped backslash
560             ## For example, this would not be skipped: something\;here
561             ## But this would be split: something\\;here resulting in something\ and here after unescaping
562             sub split_str
563             {
564 10     10 0 16 my $self = shift( @_ );
565 10         18 my $s = shift( @_ );
566 10 50       24 my $sep = @_ ? shift( @_ ) : ';';
567 10         16 my @parts = ();
568 10         16 my $i = 0;
569 10         84 foreach( split( /(\\.)|$sep/, $s ) )
570             {
571 22 100       43 defined( $_ ) ? do{ $parts[$i] .= $_ } : do{ $i++ };
  16         33  
  6         10  
572             }
573 10         35 return( @parts );
574             }
575              
576             sub _load_countries
577             {
578 294     294   593 my $self = shift( @_ );
579 294 100       741 if( !%$COUNTRIES )
580             {
581 2         5 my $in = 0;
582 2         5 my $hash = {};
583 2         467 my @data = ;
584 2         14 foreach ( @data )
585             {
586 918         1125 chomp;
587 918 100 100     1900 next unless( $in || /^\#{2} BEGIN DATA/ );
588 504 100       859 last if( /^\#{2} END DATA/ );
589 502         580 $in++;
590 502 100       771 next if( /^\#{2} BEGIN DATA/ );
591 500         2453 my( $cc, $cc3, $name, $idd ) = split( /[[:blank:]]*\;[[:blank:]]*/, $_ );
592 500 100       1315 my $keys = index( $idd, ',' ) != -1 ? [ split( /[[:blank:]]*\,[[:blank:]]*/, $idd ) ] : [ $idd ];
593 500         1585 my $info =
594             {
595             'cc' => $cc,
596             'cc3' => $cc3,
597             'name' => $name,
598             'idd' => $idd,
599             };
600 500         783 $info->{ 'idd' } = $keys;
601 500         712 foreach my $k ( @$keys )
602             {
603 514         610 my $k2 = $k;
604 514         771 $k2 =~ s/-//gs;
605 514 100       1484 $hash->{ $k2 } = [] if( !exists( $hash->{ $k2 } ) );
606 514         708 push( @{$hash->{ $k2 }}, $info );
  514         1289  
607             }
608             }
609 2         7 $COUNTRIES = $hash;
610 2         124 my @list = sort{ $b <=> $a } keys( %$hash );
  3302         3651  
611 2         73 my $re_list = join( '|', @list );
612 2         1031 $IDD_RE = qr{(?:$re_list)};
613             }
614             }
615              
616             sub _obj2h
617             {
618 0     0     my $self = shift( @_ );
619 0 0         if( UNIVERSAL::isa( $self, 'HASH' ) )
    0          
620             {
621 0           return( $self );
622             }
623             elsif( UNIVERSAL::isa( $self, 'GLOB' ) )
624             {
625 0           return( \%{*$self} );
  0            
626             }
627             ## Because object may be accessed as My::Package->method or My::Package::method
628             ## there is not always an object available, so we need to fake it to avoid error
629             ## This is primarly itended for generic methods error(), errstr() to work under any conditions.
630             else
631             {
632 0           return( {} );
633             }
634             }
635              
636             1;
637              
638             __DATA__