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.6
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   68259 use strict;
  3         19  
  3         95  
17 3     3   1319 use parent 'URI';
  3         974  
  3         16  
18 3     3   260 our( $VERSION, $VERBOSE, $DEBUG, $ERROR );
19 3         5 our( $RESERVED, $MARK, $UNRESERVED, $PCT_ENCODED, $URIC, $ALPHA, $DIGIT, $ALPHANUM, $HEXDIG );
20 3         6 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         3 our( $COUNTRIES, $IDD_RE );
22 3         10086 $VERSION = '0.6';
23             use overload ('""' => 'as_string',
24 0     0   0 '==' => sub { _obj_eq(@_) },
25 0     0   0 '!=' => sub { !_obj_eq(@_) },
26 3         30 fallback => 1,
27 3     3   26427 );
  3         5  
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 146546 my $this = shift( @_ );
108 311         589 my $str = shift( @_ );
109 311   66     1068 my $class = ref( $this ) || $this;
110 311         433 my $orig = $str;
111 311         788 $str =~ s/[[:blank:]]+//gs;
112 311         488 my $temp = {};
113 311         506 my @matches = ();
114 311         421 my @names = ();
115 311 100       5341 if( @matches = $str =~ /^((?:tel\:)?$GLOBAL_NUMBER)$/ )
    100          
    100          
    100          
116             {
117 270         645 @names = qw( all subscriber params last_param );
118 270         589 $temp->{ 'type' } = 'global';
119             }
120             elsif( @matches = $str =~ /^((?:tel\:)?$LOCAL_NUMBER)$/ )
121             {
122 5         16 $temp->{ 'type' } = 'local';
123 5         17 @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         80 $temp->{ 'type' } = 'other';
129 31         69 @names = qw( all subscriber params ignore4 last_param );
130             }
131             elsif( @matches = $str =~ /^((?:tel\:)?$VANITY)$/ )
132             {
133 2         6 $temp->{ 'type' } = 'vanity';
134 2         5 @names = qw( all subscriber );
135             }
136             else
137             {
138 3         10 $ERROR = "Unknown telephone number '$str'.";
139 3         80 warn( $ERROR );
140             }
141            
142 311 100       1013 if( $str =~ /^(?:tel\:)?\+/ )
143             {
144             ## Extract the global idd
145 271         680 $this->_load_countries;
146 271         1341 ( my $str2 = $str ) =~ s/[\Q$VISUAL_SEPARATOR\E]+//g;
147 271         525 $str2 =~ s/^tel\://;
148 271 50       1784 if( $str2 =~ /^\+($IDD_RE)/ )
149             {
150 271         640 my $idd = $1;
151 271 50       673 if( CORE::exists( $COUNTRIES->{ $idd } ) )
152             {
153 271         614 my $idds = $COUNTRIES->{ $idd }->[0]->{ 'idd' };
154 271         490 foreach my $thisIdd ( @$idds )
155             {
156 280         403 my $check = $thisIdd;
157 280         494 $check =~ s/\D//g;
158 280 100       516 if( $check eq $idd )
159             {
160 271         626 $temp->{ 'context' } = '+' . $thisIdd;
161 271         793 last;
162             }
163             }
164             }
165             }
166             }
167            
168             ## The component name for each match
169 311         1102 @$temp{ @names } = @matches;
170 311 100       975 $temp->{ 'params' } = $temp->{ 'params1' } ? $temp->{ 'params1' } : $temp->{ 'params2' } if( !length( $temp->{ 'params' } ) );
    100          
171 311         605 $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     3076 'context' => $temp->{ 'context' },
    100          
    100          
    100          
    100          
    100          
    100          
183             };
184 311         614 my $self = bless( $hash, $class );
185 311         465 my $prams = [];
186 311 100       613 if( length( $temp->{ 'params' } ) )
187             {
188 10         15 my $pram_str = $temp->{ 'params' };
189 10         29 $pram_str =~ s/^[\.\,\#\;]//;
190 10         27 $prams = [ $self->split_str( $pram_str ) ];
191             }
192             ## Private parameters
193 311         551 my $priv = {};
194 311         564 foreach my $this ( @$prams )
195             {
196 16         68 $this =~ s/^(x|ext)\.?(\d+)$/ext=$2/i;
197 16         45 my( $p, $v ) = split( /=/, $this, 2 );
198 16         35 $p =~ s/\-/\_/gs;
199 16 100       63 if( lc( $p ) =~ /^(ext|isdn_subaddress)$/ )
    100          
200             {
201 10         30 $hash->{ lc( $p ) } = $v;
202             }
203             elsif( lc( $p ) eq 'phone_context' )
204             {
205 2         6 $hash->{ 'context' } = $v;
206 2         5 $temp->{ '_has_context_param' } = 1;
207             }
208             else
209             {
210 4         12 $priv->{ lc( $p ) } = $v;
211             }
212             }
213 311         578 $self->{ 'private' } = $priv;
214 311 100 100     1092 $self->{ 'ext' } = $temp->{ 'ext' } if( !length( $hash->{ 'ext' } ) && !$self->{ 'is_vanity' } );
215 311         545 $self->{ 'ext' } =~ s/\D//gs;
216 311 100       613 $self->{ '_prepend_context' } = $temp->{ '_has_context_param' } ? 0 : 1;
217 311         1361 return( $self );
218             }
219              
220             sub as_string
221             {
222 48     48 1 17070 my $self = shift( @_ );
223 48 100       137 return( $self->{ 'cache' } ) if( length( $self->{ 'cache' } ) );
224 47 100       161 my @uri = ( 'tel:' . $self->{ 'subscriber' } ) if( length( $self->{ 'subscriber' } ) );
225 47         70 my @params = ();
226 47 100       136 push( @params, sprintf( "ext=%s", $self->{ 'ext' } ) ) if( length( $self->{ 'ext' } ) );
227 47 50       93 push( @params, sprintf( "isub=%s", $self->{ 'isdn_subaddress' } ) ) if( length( $self->{ 'isdn_subaddress' } ) );
228 47 100       76 if( length( $self->{ 'context' } ) )
229             {
230 31 100 66     133 if( !$self->{ '_prepend_context' } )
    100          
231             {
232 15         80 push( @params, sprintf( "phone-context=%s", $self->{ 'context' } ) );
233             }
234             elsif( $self->{ 'subscriber' } !~ /^\+\d+/ && $self->{ 'context' } !~ /[a-zA-Z]+/ )
235             {
236 1         6 @uri = ( 'tel:' . $self->{ 'context' } . '.' . $self->{ 'subscriber' } );
237             }
238             }
239 47         74 my $priv = $self->{ 'private' };
240 47         137 foreach my $k ( sort( keys( %$priv ) ) )
241             {
242 8 50       32 push( @params, sprintf( "$k=%s", $priv->{ $k } ) ) if( length( $priv->{ $k } ) );
243             }
244 47 100       125 push( @uri, join( ';', @params ) ) if( scalar( @params ) );
245 47         134 $self->{ 'cache' } = join( ';', @uri );
246 47         391 return( $self->{ 'cache' } );
247             }
248              
249             *letters2digits = \&aton;
250              
251             sub aton
252             {
253 42     42 1 58 my $self = shift( @_ );
254 42   66     155 my $str = shift( @_ ) || $self->{ 'subscriber' };
255 42         71 my $letters = 'abcdefghijklmnopqrstuvwxyz';
256 42         55 my $digits = '22233344455566677778889999';
257 42 100 66     199 return( $str ) if( $str !~ /[a-zA-Z]+/ || !$self->is_vanity );
258 4         9 $str = lc( $str );
259 4         9 my $res = '';
260 4         10 for( my $i = 0; $i < length( $str ); $i++ )
261             {
262 52         68 my $c = substr( $str, $i, 1 );
263 52         66 my $p = index( $letters, $c );
264 52 100       108 $res .= $p != -1 ? substr( $digits, $p, 1 ) : $c;
265             }
266 4         9 return( $res );
267             }
268              
269             sub canonical
270             {
271 42     42 1 1646 my $self = shift( @_ );
272 42         76 my $tel = $self->aton;
273 42         336 $tel =~ s/[\Q$VISUAL_SEPARATOR\E]+//gs;
274 42         127 my $uri = $self->new( "tel:$tel" );
275 42 100       129 $uri->ext( $self->{ 'ext' } ) if( length( $self->{ 'ext' } ) );
276 42 50       83 $uri->isub( $self->{ 'isdn_subaddress' } ) if( length( $self->{ 'isdn_subaddress' } ) );
277 42 100       109 $uri->context( $self->{ 'context' } ) if( length( $self->{ 'context' } ) );
278 42         64 $uri->{_has_context_param} = $self->{_has_context_param};
279 42         141 $uri->{_prepend_context} = $self->{_prepend_context};
280 42         68 my $priv = $self->{ 'private' };
281 42         89 %{$uri->{ 'private' }} = %$priv;
  42         79  
282 42         130 return( $uri );
283             }
284              
285             sub cc2context
286             {
287 1     1 0 7 my $self = shift( @_ );
288 1         4 my $cc = uc( shift( @_ ) );
289 1 50       4 return( $self->error( "No country code provided." ) ) if( !length( $cc ) );
290 1         3 $self->_load_countries;
291 1         2 my $hash = $COUNTRIES;
292 1         101 foreach my $k ( sort( keys( %$hash ) ) )
293             {
294             ## array ref
295 205         261 my $ref = $hash->{ $k };
296 205         284 foreach my $this ( @$ref )
297             {
298 217 100       445 if( $this->{ 'cc' } eq $cc )
299             {
300 1         7 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 1488 my $self = shift( @_ );
322 293 100       587 if( @_ )
323             {
324 27         38 my $str = shift( @_ );
325 27 50       396 if( $str !~ /^$DESCRIPTOR$/ )
326             {
327 0         0 warn( "'$str' is not a valid context\n" );
328 0         0 return( undef() );
329             }
330 27         47 delete( $self->{ 'cache' } );
331 27         44 $self->{ '_has_context_param' } = 1;
332 27         38 $self->{ '_prepend_context' } = 0;
333 27         48 $self->{ 'context' } = $str;
334             }
335 293         695 return( $self->{ 'context' } );
336             }
337              
338             sub country
339             {
340 21     21 1 38 my $self = shift( @_ );
341 21         56 $self->_load_countries;
342 21         27 my $hash = $COUNTRIES;
343 21 100       82 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         44 my $uri = $self->canonical;
347 21 100       67 $idd = substr( $uri->{ 'subscriber' }, 0, 1 ) eq '+' ? $uri->{ 'subscriber' } : substr( $uri->{ 'context' }, 0, 1 ) eq '+' ? $uri->{ 'context' } : '';
    100          
348             ## Remove the '+'
349 21         41 $idd = substr( $idd, 1 );
350 21         593 foreach my $k ( %$hash )
351             {
352 6054 100       10479 next if( length( $k ) > length( $idd ) );
353             ## We found a match
354             ## We return all the countries that match the international prefix
355 322 100       546 if( substr( $idd, 0, length( $k ) ) eq $k )
356             {
357 10         14 my $ref = $hash->{ $k };
358 10 50       123 return( wantarray() ? @$ref : \@$ref );
359             }
360             }
361             ## We got here, nothing found
362 11 50       135 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 388 my $self = shift( @_ );
394 43 100       94 if( @_ )
395             {
396 21         30 my $val = shift( @_ );
397 21 50 33     169 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         36 delete( $self->{ 'cache' } );
403 21         39 $self->{ 'ext' } = $val;
404             }
405 43         129 return( $self->{ 'ext' } );
406             }
407              
408             sub is_global
409             {
410 21     21 1 11493 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       73 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         117 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       5 if( @_ )
455             {
456 1         4 $self->{ '_prepend_context' } = shift( @_ );
457 1         3 delete( $self->{ 'cache' } );
458             }
459 1         2 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       7 return( wantarray() ? %{$self->{ 'private' }} : \%{$self->{ 'private' }} );
  0         0  
  3         10  
498             }
499              
500             sub subscriber
501             {
502 21     21 1 41 my $self = shift( @_ );
503 21 50       54 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         91 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   27 no overloading;
  3         5  
  3         716  
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   24 use overloading;
  3         7  
  3         2098  
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         15 my $s = shift( @_ );
566 10 50       22 my $sep = @_ ? shift( @_ ) : ';';
567 10         19 my @parts = ();
568 10         15 my $i = 0;
569 10         81 foreach( split( /(\\.)|$sep/, $s ) )
570             {
571 22 100       37 defined( $_ ) ? do{ $parts[$i] .= $_ } : do{ $i++ };
  16         33  
  6         10  
572             }
573 10         36 return( @parts );
574             }
575              
576             sub _load_countries
577             {
578 294     294   623 my $self = shift( @_ );
579 294 100       770 if( !%$COUNTRIES )
580             {
581 2         5 my $in = 0;
582 2         3 my $hash = {};
583 2         439 my @data = ;
584 2         15 foreach ( @data )
585             {
586 918         1124 chomp;
587 918 100 100     1872 next unless( $in || /^\#{2} BEGIN DATA/ );
588 504 100       874 last if( /^\#{2} END DATA/ );
589 502         562 $in++;
590 502 100       796 next if( /^\#{2} BEGIN DATA/ );
591 500         2406 my( $cc, $cc3, $name, $idd ) = split( /[[:blank:]]*\;[[:blank:]]*/, $_ );
592 500 100       1343 my $keys = index( $idd, ',' ) != -1 ? [ split( /[[:blank:]]*\,[[:blank:]]*/, $idd ) ] : [ $idd ];
593 500         1605 my $info =
594             {
595             'cc' => $cc,
596             'cc3' => $cc3,
597             'name' => $name,
598             'idd' => $idd,
599             };
600 500         747 $info->{ 'idd' } = $keys;
601 500         732 foreach my $k ( @$keys )
602             {
603 514         638 my $k2 = $k;
604 514         747 $k2 =~ s/-//gs;
605 514 100       1362 $hash->{ $k2 } = [] if( !exists( $hash->{ $k2 } ) );
606 514         673 push( @{$hash->{ $k2 }}, $info );
  514         1386  
607             }
608             }
609 2         4 $COUNTRIES = $hash;
610 2         110 my @list = sort{ $b <=> $a } keys( %$hash );
  3279         3660  
611 2         61 my $re_list = join( '|', @list );
612 2         985 $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__