File Coverage

blib/lib/MarpaX/ESLIF/URI/tel.pm
Criterion Covered Total %
statement 98 154 63.6
branch 17 44 38.6
condition 4 33 12.1
subroutine 30 42 71.4
pod 18 18 100.0
total 167 291 57.3


line stmt bran cond sub pod time code
1 1     1   740 use strict;
  1         3  
  1         35  
2 1     1   6 use warnings FATAL => 'all';
  1         1  
  1         70  
3              
4             package MarpaX::ESLIF::URI::tel;
5              
6             # ABSTRACT: URI::tel syntax as per RFC3966, RFC4694, RFC4715, RFC4759, RFC4904
7              
8             our $AUTHORITY = 'cpan:JDDPAUSE'; # AUTHORITY
9              
10             our $VERSION = '0.007'; # VERSION
11              
12 1     1   9 use Carp qw/croak/;
  1         3  
  1         64  
13 1     1   8 use Class::Tiny::Antlers;
  1         2  
  1         8  
14 1     1   223 use MarpaX::ESLIF;
  1         3  
  1         2166  
15              
16             extends 'MarpaX::ESLIF::URI::_generic';
17              
18             has '_number' => (is => 'rwp');
19             has '_is_global' => (is => 'rwp');
20             has '_is_local' => (is => 'rwp');
21             has '_ext' => (is => 'rwp');
22             has '_isub' => (is => 'rwp');
23             has '_isub_encoding' => (is => 'rwp');
24             has '_tgrp' => (is => 'rwp');
25             has '_trunk_context' => (is => 'rwp');
26             has '_phone_context' => (is => 'rwp');
27             has '_rn' => (is => 'rwp');
28             has '_rn_context' => (is => 'rwp');
29             has '_cic' => (is => 'rwp');
30             has '_cic_context' => (is => 'rwp');
31             has '_has_npdi' => (is => 'rwp');
32             has '_has_enumdi' => (is => 'rwp');
33             has '_parameters' => (is => 'rwp', default => sub { { origin => [], decoded => [], normalized => [] } });
34              
35             #
36             # Constants
37             #
38             my $BNF = do { local $/; <DATA> };
39             my $GRAMMAR = MarpaX::ESLIF::Grammar->new(__PACKAGE__->eslif, __PACKAGE__->bnf);
40              
41              
42             sub bnf {
43 1     1 1 2 my ($class) = @_;
44              
45 1         6 join("\n", $BNF, MarpaX::ESLIF::URI::_generic->bnf)
46             };
47              
48              
49             sub grammar {
50 8     8 1 20 my ($class) = @_;
51              
52 8         417 return $GRAMMAR;
53             }
54              
55              
56             sub number {
57 24     24 1 14163 my ($self, $type) = @_;
58              
59 24         80 return $self->_generic_getter('_number', $type)
60             }
61              
62              
63             sub is_global {
64 8     8 1 3854 my ($self) = @_;
65              
66             return $self->{_is_global}
67 8         27 }
68              
69              
70             sub is_local {
71 8     8 1 4470 my ($self) = @_;
72              
73             return $self->{_is_local}
74 8         24 }
75              
76              
77             sub ext {
78 0     0 1 0 my ($self, $type) = @_;
79              
80 0         0 return $self->_generic_getter('_ext', $type)
81             }
82              
83              
84             sub isub {
85 0     0 1 0 my ($self, $type) = @_;
86              
87 0         0 return $self->_generic_getter('_isub', $type)
88             }
89              
90              
91             sub isub_encoding {
92 3     3 1 1687 my ($self, $type) = @_;
93              
94 3         14 return $self->_generic_getter('_isub_encoding', $type)
95             }
96              
97              
98             sub tgrp {
99 0     0 1 0 my ($self, $type) = @_;
100              
101 0         0 return $self->_generic_getter('_tgrp', $type)
102             }
103              
104              
105             sub trunk_context {
106 0     0 1 0 my ($self, $type) = @_;
107              
108 0         0 return $self->_generic_getter('_trunk_context', $type)
109             }
110              
111              
112             sub phone_context {
113 6     6 1 6312 my ($self, $type) = @_;
114              
115 6         39 return $self->_generic_getter('_phone_context', $type)
116             }
117              
118              
119             sub rn {
120 3     3 1 3161 my ($self, $type) = @_;
121              
122 3         11 return $self->_generic_getter('_rn', $type)
123             }
124              
125              
126              
127             sub rn_context {
128 0     0 1 0 my ($self, $type) = @_;
129              
130 0         0 return $self->_generic_getter('_rn_context', $type)
131             }
132              
133              
134              
135             sub cic {
136 3     3 1 1832 my ($self, $type) = @_;
137              
138 3         13 return $self->_generic_getter('_cic', $type)
139             }
140              
141              
142             sub cic_context {
143 0     0 1 0 my ($self, $type) = @_;
144              
145 0         0 return $self->_generic_getter('_cic_context', $type)
146             }
147              
148              
149             sub has_npdi {
150 7     7 1 3370 my ($self) = @_;
151              
152             return $self->{_has_npdi}
153 7         20 }
154              
155              
156             sub has_enumdi {
157 7     7 1 3977 my ($self) = @_;
158              
159             return $self->{_has_enumdi}
160 7         23 }
161              
162              
163             sub parameters {
164 21     21 1 29071 my ($self, $type) = @_;
165              
166 21         74 return $self->_generic_getter('_parameters', $type)
167             }
168              
169             # ------------------------
170             # Specific grammar actions
171             # ------------------------
172             sub __number {
173 11     11   28 my ($self, @args) = @_;
174              
175 11         31 my $rc = $self->__concat(@args);
176             #
177             # Normalizer number is without the visual separators
178             #
179 11         70 $rc->{normalized} =~ s/[-.()]//g;
180              
181 11         135 return $rc
182             }
183              
184             sub __global {
185 6     6   18 my ($self, $global_number_digits, @rest) = @_;
186              
187 6         18 $self->{_is_global} = 1;
188 6         15 $self->{_number} = $global_number_digits;
189              
190 6         19 return $self->__concat($global_number_digits, @rest)
191             }
192              
193             sub __local {
194 2     2   7 my ($self, $local_number_digits, @rest) = @_;
195              
196 2         9 $self->{_is_local} = 1;
197 2         7 $self->{_number} = $local_number_digits;
198              
199 2         8 return $self->__concat($local_number_digits, @rest)
200             }
201              
202             sub __pname {
203 0     0   0 my ($self, @args) = @_;
204             #
205             # Normalized <pname> is case-insensitive.
206             #
207 0         0 my $rc = $self->__concat(@args);
208              
209 0         0 return $rc
210             }
211              
212             sub __parameter_cmp {
213 0     0   0 my ($parametera, $parameterb) = @_;
214              
215 0         0 my $keya = $parametera->{key};
216 0         0 my $keyb = $parameterb->{key};
217              
218 0 0 0     0 if (($keya eq 'ext') or ($keya eq 'isub')) {
    0          
    0          
219 0 0 0     0 if (($keyb eq 'ext') or ($keyb eq 'isub')) {
220             #
221             # ext will naturally come before isub
222             #
223 0         0 return $keya cmp $keyb
224             } else {
225             #
226             # ext or isub always comes first
227             #
228 0         0 return 1
229             }
230             } elsif ($keya eq 'phone-context') {
231             #
232             # phone-context always appear after ext or isub, if any, and before any other parameter
233             #
234 0 0 0     0 if (($keyb eq 'ext') or ($keyb eq 'isub')) {
235 0         0 return -1
236             } else {
237 0         0 return 1
238             }
239             } elsif ($keyb eq 'phone-context') {
240             #
241             # phone-context always appear after ext or isub, if any, and before any other parameter
242             #
243 0 0 0     0 if (($keya eq 'ext') or ($keya eq 'isub')) {
244 0         0 return 1
245             } else {
246 0         0 return -1
247             }
248             } else {
249 0         0 return $keya cmp $keyb
250             }
251             }
252              
253             sub __parameter {
254 10     10   25 my ($self, $semicolumn, $pname, $equal, $pvalue) = @_; # $equal and $pvalue may be undef
255             #
256             # Each parameter name ("pname"), the ISDN subaddress, the 'extension',
257             # and the 'context' MUST NOT appear more than once. The 'isdn-
258             # subaddress' or 'extension' MUST appear first, if present, followed by
259             # the 'context' parameter, if present, followed by any other parameters
260             # in lexicographical order.
261             #
262 10         36 my $concat = $self->__concat($semicolumn, $pname, $equal, $pvalue);
263              
264 10         19 foreach my $type (qw/normalized origin decoded/) { # C.f. __add_parameter for normalization
265 30         216 my $key = $pname->{$type};
266 30 100       71 my $value = defined($pvalue) ? $pvalue->{$type} : undef;
267             #
268             # We compare using the normalized type
269             #
270 30 100       68 if ($type eq 'normalized') {
271 10         20 my $keyNotNormalized = $pname->{origin};
272             #
273             # A parameter must not appear more than once - this makes sure that
274             # reserved keywords coming from unwanted rule par ::= parameter are
275             # catched, e.g. 'Ext' alone
276             #
277 10 50       17 if (grep {$_ eq $key} map { $_->{key} } @{$self->_parameters->{$type}}) {
  4 100       13  
  4         36  
  10         238  
278 0         0 croak "Parameter '$keyNotNormalized' already exists"
279 10         218 } elsif (@{$self->_parameters->{$type}}) {
280 3 50 33     60 if (($key eq 'ext') || ($key eq 'isub')) {
    50          
281             #
282             # isub or ext must appear first
283             #
284 0         0 my $previouskey = $self->_parameters->{$type}->[-1]->{key};
285 0 0 0     0 if (($previouskey ne 'ext') && ($previouskey ne 'isub')) {
286 0         0 my $previouskeyNotNormalized = $self->_parameters->{origin}->[-1]->{key};
287 0         0 croak "Parameter '$keyNotNormalized' must appear before '$previouskeyNotNormalized'"
288             }
289             } elsif ($key eq 'phone-context') {
290             #
291             # context parameter must be after isub or ext if present
292             #
293 0         0 my $max = -1;
294 0         0 my $firstkey = $self->_parameters->{$type}->[0]->{key};
295 0 0 0     0 if (($firstkey eq 'ext') || ($firstkey eq 'isub')) {
296 0 0       0 if ($#{$self->_parameters->{$type}} > 0) {
  0         0  
297 0         0 my $secondkey = $self->_parameters->{$type}->[1]->{key};
298 0 0 0     0 if (($secondkey eq 'ext') || ($secondkey eq 'isub')) {
299 0         0 $max = 1;
300             } else {
301 0         0 $max = 0;
302             }
303             }
304             }
305 0 0 0     0 if (($max >= 0) && ($#{$self->_parameters->{$type}} != $max)) {
  0         0  
306 0         0 my $targetkeyNotNormalized = $self->_parameters->{origin}->[$max]->{key};
307 0         0 croak "Parameter '$keyNotNormalized' must appear after '$targetkeyNotNormalized'"
308             }
309             } else {
310             #
311             # Any other must be in lexicographical order
312             #
313 3         55 my $previouskey = $self->_parameters->{$type}->[-1]->{key};
314 3 100 33     95 if (($previouskey ne 'ext') && ($previouskey ne 'isub') && ($previouskey ne 'phone-context')) {
      66        
315 2 50       19 if (($previouskey cmp $key) >= 0) {
316 0         0 croak "Parameter '$keyNotNormalized' must appear before previous parameter '$previouskey'"
317             }
318             }
319             }
320             }
321             }
322              
323 30         101 push(@{$self->_parameters->{$type}}, { key => $key, value => $value });
  30         574  
324             }
325              
326 10         216 return $concat
327             }
328              
329             my $semicolumn = { normalized => ';', origin => ';', decoded => ';' };
330             my $equal = { normalized => '=', origin => '=', decoded => '=' };
331             sub __add_parameter {
332 10     10   42 my ($self, $name, $pvalue) = @_;
333              
334 10         20 my %pname;
335 10         25 foreach my $type (qw/normalized origin decoded/) {
336 30         86 $pname{$type} = $name->{$type};
337 30 50       111 substr($pname{$type}, 0, 1, '') if substr($pname{$type}, 0, 1) eq ';';
338 30 100       100 substr($pname{$type}, -1, 1, '') if substr($pname{$type}, -1, 1) eq '=';
339             }
340              
341 10         29 $pname{normalized} = lc($pname{normalized});
342 10 100       23 if (defined($pvalue)) {
343             $pvalue->{normalized} = lc($pvalue->{normalized})
344 7         16 }
345              
346 10         35 return $self->__parameter($semicolumn, \%pname, $equal, $pvalue)
347             }
348              
349             sub __ext {
350 0     0   0 my ($self, $ext, $pvalue) = @_;
351              
352 0         0 return $self->__add_parameter($ext, $self->{_ext} = $pvalue)
353             }
354              
355             sub __isub {
356 0     0   0 my ($self, $isub, $pvalue) = @_;
357              
358 0         0 return $self->__add_parameter($isub, $self->{_isub} = $pvalue)
359             }
360              
361             sub __tgrp {
362 1     1   5 my ($self, $tgrp, $pvalue) = @_;
363              
364 1         4 return $self->__add_parameter($tgrp, $self->{_tgrp} = $pvalue)
365             }
366              
367             sub __trunk_context {
368 1     1   5 my ($self, $trunk_context, $pvalue) = @_;
369              
370 1         5 return $self->__add_parameter($trunk_context, $self->{_trunk_context} = $pvalue)
371             }
372              
373             sub __phone_context {
374 2     2   6 my ($self, $phone_context, $pvalue) = @_;
375              
376 2         11 return $self->__add_parameter($phone_context, $self->{_phone_context} = $pvalue)
377             }
378              
379             sub __rn {
380 1     1   5 my ($self, $rn, $pvalue) = @_;
381              
382 1         5 return $self->__add_parameter($rn, $self->{_rn} = $pvalue)
383             }
384              
385             sub __rn_context {
386 0     0   0 my ($self, $rn_context, $pvalue) = @_;
387              
388 0         0 return $self->__add_parameter($rn_context, $self->{_rn_context} = $pvalue)
389             }
390              
391             sub __npdi {
392 2     2   5 my ($self, $npdi) = @_;
393              
394 2         6 $self->{_has_npdi} = 1;
395              
396 2         8 return $self->__add_parameter($npdi)
397             }
398              
399             sub __cic {
400 1     1   4 my ($self, $cic, $pvalue) = @_;
401              
402 1         5 return $self->__add_parameter($cic, $self->{_cic} = $pvalue)
403             }
404              
405             sub __cic_context {
406 0     0   0 my ($self, $cic_context, $pvalue) = @_;
407              
408 0         0 return $self->__add_parameter($cic_context, $self->{_cic_context} = $pvalue)
409             }
410              
411             sub __isub_encoding {
412 1     1   4 my ($self, $isub_encoding, $pvalue) = @_;
413              
414 1         5 return $self->__add_parameter($isub_encoding, $self->{_isub_encoding} = $pvalue)
415             }
416              
417             sub __enumdi {
418 1     1   4 my ($self, $enumdi) = @_;
419              
420 1         3 $self->{_has_enumdi} = 1;
421              
422 1         3 return $self->__add_parameter($enumdi)
423             }
424              
425              
426             1;
427              
428             =pod
429              
430             =encoding UTF-8
431              
432             =head1 NAME
433              
434             MarpaX::ESLIF::URI::tel - URI::tel syntax as per RFC3966, RFC4694, RFC4715, RFC4759, RFC4904
435              
436             =head1 VERSION
437              
438             version 0.007
439              
440             =head1 SUBROUTINES/METHODS
441              
442             MarpaX::ESLIF::URI::tel inherits, and eventually overwrites some, methods of MarpaX::ESLIF::URI::_generic.
443              
444             =head2 $class->bnf
445              
446             Overwrites parent's bnf implementation. Returns the BNF used to parse the input.
447              
448             =head2 $class->grammar
449              
450             Overwrite parent's grammar implementation. Returns the compiled BNF used to parse the input as MarpaX::ESLIF::Grammar singleton.
451              
452             =head2 $self->number($type)
453              
454             Returns the global or local number digits. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
455              
456             =head2 $self->is_global()
457              
458             Returns a true value if number is global, else a false value.
459              
460             =head2 $self->is_local()
461              
462             Returns a true value if number is local, else a false value.
463              
464             =head2 $self->ext($type)
465              
466             Returns the extension, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
467              
468             =head2 $self->isub($type)
469              
470             Returns the isdn sub-address, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
471              
472             =head2 $self->isub_encoding($type)
473              
474             Returns the isdn sub-address encoding for transmission, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
475              
476             =head2 $self->tgrp($type)
477              
478             Returns the trunk group, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
479              
480             =head2 $self->trunk_context($type)
481              
482             Returns the trunk context, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
483              
484             =head2 $self->phone_context($type)
485              
486             Returns the phone context, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
487              
488             =head2 $self->rn($type)
489              
490             Returns the rn, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
491              
492             =head2 $self->rn_context($type)
493              
494             Returns the rn context, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
495              
496             =head2 $self->cic($type)
497              
498             Returns the cic, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
499              
500             =head2 $self->cic_context($type)
501              
502             Returns the cic context, if any. May be undef. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
503              
504             =head2 $self->has_npdi()
505              
506             Returns a true value if the URI has the npdi parameter, else a false value.
507              
508             =head2 $self->has_enumdi()
509              
510             Returns a true value if the URI has the enumdi parameter, else a false value.
511              
512             =head2 $self->parameters($type)
513              
514             Returns the parameters as an array of hashes that have the form { key => $key, value => $value }, where value may be undef, and with respect to the order of appearance in the URI. C<$type> is either 'decoded' (default value), 'origin' or 'normalized'.
515              
516             =head1 NOTES
517              
518             =over
519              
520             =item
521              
522             Errata L<203|https://www.rfc-editor.org/errata/eid203> has been applied.
523              
524             =item
525              
526             Parameters are NOT reordered. So, since RFC3966 states that they B<MUST> appear in lexicographical order (except for C<ext>, C<isdn> and C<phone-context>), the parsing will fail in the input does not respect this sorting rule.
527              
528             =item
529              
530             RFC4694 requires compliance with L<E.164|https://en.wikipedia.org/wiki/E.164> but this is not checked.
531              
532             =item
533              
534             Any other extension, like premium rate category ("premrate" parameter), calling number verification ("verstat" parameter) etc... is not explicitly included unless an L<IETF|https://tools.ietf.org/> exists. Note that all known extensions are implicitly supported as long as their specification is just an extensions of the "parameter" or "par" rules.
535              
536             =back
537              
538             =head1 SEE ALSO
539              
540             tel URI is totally case insensitive.
541              
542             =head1 AUTHOR
543              
544             Jean-Damien Durand <jeandamiendurand@free.fr>
545              
546             =head1 COPYRIGHT AND LICENSE
547              
548             This software is copyright (c) 2017 by Jean-Damien Durand.
549              
550             This is free software; you can redistribute it and/or modify it under
551             the same terms as the Perl 5 programming language system itself.
552              
553             =cut
554              
555             __DATA__
556             #
557             # Reference: https://tools.ietf.org/html/rfc3966#section-3
558             #
559             <telephone URI> ::= <telephone scheme> ":" <telephone subscriber> action => _action_string
560              
561             <telephone scheme> ::= "tel":i action => _action_scheme
562              
563             <telephone subscriber> ::= <global number>
564             | <local number>
565              
566             <global number> ::= <global number digits> pars action => __global
567             <local number> ::= <local number digits> pars context pars action => __local
568             pars ::= par*
569             par ::= parameter
570             | extension
571             | <isdn subaddress>
572             | <trunk group>
573             | <trunk context>
574             <isdn subaddress> ::= ";isub=":i <uric many> action => __isub
575             <trunk group> ::= ";tgrp=":i <trunk group label> action => __tgrp
576             <trunk context> ::= ";trunk-context=":i descriptor action => __trunk_context
577             <trunk group label unit> ::= unreserved
578             | <pct encoded>
579             | <trunk group unreserved>
580             <trunk group unreserved> ::= [/&+$]
581             <trunk group label> ::= <trunk group label unit>+
582             extension ::= ";ext=":i <phonedigit many> action => __ext
583             context ::= ";phone-context=":i descriptor action => __phone_context
584             descriptor ::= domainname
585             | <global number digits>
586             #
587             # The <global number digits> and <local number digits> are ambiguous because
588             # <phonedigit> contains DIGIT, and <phonedigit hex> contains HEXDIG
589             #
590             # What W3C wanted to express with <global number digits> is that it must contains
591             # at least one DIGIT everywhere
592             # Original expression was: <global number digits> ::= "+" <phonedigit any> DIGIT <phonedigit any>
593             # Fixed expression is taking advantage of the greedy nature of regexp:
594             <global number digits> ::= /\+[0-9.()-]*[0-9][0-9.()-]*/ action => __number
595              
596             #
597             # Same remark for <local number digits>: <phonedigit hex>
598             # Original expression was: <local number digits> ::= <phonedigit hex any> <local number digits sep> <phonedigit hex any>
599             # Fixed expression is:
600             <local number digits> ::= /[0-9A-Fa-f*#.()-]*[0-9A-Fa-f*#][0-9A-Fa-f*#.()-]*/ action => __number
601             # <local number digits sep> ::= HEXDIG
602             # | "*"
603             # | "#"
604             <domainlabel and dot> ::= domainlabel "."
605             <domainlabels> ::= <domainlabel and dot>*
606             domainname ::= <domainlabels> toplabel "."
607             | <domainlabels> toplabel
608             domainlabel ::= /[A-Za-z0-9-](?:[A-Za-z0-9-]*[A-Za-z0-9])?/
609             toplabel ::= /[A-Za-z](?:[A-Za-z0-9-]*[A-Za-z0-9])?/
610             parameter ::= ";" pname action => __parameter
611             | ";" pname "=" pvalue action => __parameter
612             pname ::= /[A-Za-z0-9-]+/ action => __pname
613             pvalue ::= <paramchar many>
614             paramchar ::= <param unreserved>
615             | <tel unreserved>
616             | <pct encoded>
617             <paramchar many> ::= paramchar+
618             <tel unreserved> ::= alphanum
619             | mark
620             mark ::= [-_.!~*'()]
621             <param unreserved> ::= [\[\]/:&+$]
622             phonedigit ::= DIGIT
623             | <visual separator>
624             <phonedigit many> ::= phonedigit+ action => __number
625             <visual separator> ::= [-.()]
626             alphanum ::= [A-Za-z0-9]
627             <tel reserved> ::= [;/?:@&=+$,]
628             uric ::= <unreserved>
629             | <pct encoded>
630             | <tel reserved>
631             <uric many> ::= uric+
632              
633             #
634             ## RFC 4694
635             #
636             parameter ::= rn
637             | cic
638             | npdi
639             rn ::= ";rn=":i <global rn> action => __rn
640             | ";rn=":i <local rn> action => __rn
641             npdi ::= ";npdi":i action => __npdi
642             cic ::= ";cic=":i <global cic> action => __cic
643             | ";cic=":i <local cic> action => __cic
644             <global rn> ::= <global hex digits>
645             # The first "hex-phonedigit" value in "local-rn" MUST be a hex-decimal digit.
646             <local rn> ::= HEXDIG <hex phonedigit any> <rn context>
647             <rn context> ::= ";rn-context=":i <rn descriptor> action => __rn_context
648             <rn descriptor> ::= domainname
649             | <global hex digits>
650             <global hex digits> ::= "+" /[0-9]{1,3}/ <hex phonedigit any>
651             <hex phonedigit> ::= HEXDIG
652             | <visual separator>
653             <global cic> ::= <global hex digits>
654             # The first "hex-phonedigit" value in "local-rn" MUST be a hex-decimal digit.
655             <local cic> ::= HEXDIG <hex phonedigit any> <cic context>
656             <cic context> ::= ";cic-context=":i <rn descriptor> action => __cic_context
657              
658             <hex phonedigit any> ::= <hex phonedigit>* action => __number
659              
660             #
661             # RFC4715
662             #
663             parameter ::= ";isub-encoding=":i <isub encoding value> action => __isub_encoding
664             #
665             # No need to set "nsap-ia5", "nsap-bcd" or "nsap" explicitly: rfc4715token will catch them
666             <isub encoding value> ::= rfc4715token
667             rfc4715token ::= <uric many>
668              
669             #
670             ## RFC 4759
671             #
672             parameter ::= enumdi
673             enumdi ::= ";enumdi":i action => __enumdi