File Coverage

lib/Data/Identifier/Util.pm
Criterion Covered Total %
statement 124 291 42.6
branch 77 202 38.1
condition 13 83 15.6
subroutine 16 26 61.5
pod 11 13 84.6
total 241 615 39.1


line stmt bran cond sub pod time code
1             # Copyright (c) 2023-2026 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: format independent identifier object
6              
7              
8             package Data::Identifier::Util;
9              
10 2     2   219837 use v5.14;
  2         7  
11 2     2   60 use strict;
  2         6  
  2         58  
12 2     2   10 use warnings;
  2         2  
  2         97  
13              
14 2     2   9 use parent qw(Data::Identifier::Interface::Userdata Data::Identifier::Interface::Subobjects);
  2         3  
  2         14  
15              
16 2     2   124 use Carp;
  2         5  
  2         129  
17              
18 2     2   505 use Data::Identifier;
  2         6  
  2         16  
19 2     2   577 use Data::Identifier::Generate;
  2         4  
  2         160  
20              
21             our $VERSION = v0.30;
22              
23             use constant {
24 2         9 BOOL_TRUE => Data::Identifier->new(uuid => 'eb50b3dc-28be-4cfc-a9ea-bd7cee73aed5')->register,
25             BOOL_FALSE => Data::Identifier->new(uuid => '6d34d4a1-8fbc-4e22-b3e0-d50f43d97cb1')->register,
26 2     2   13 };
  2         3  
27              
28             _update_tag(BOOL_TRUE, 46, 190, 'true');
29             _update_tag(BOOL_FALSE, 45, 189, 'false');
30              
31             my @truths = (BOOL_TRUE, qw(https://schema.org/True http://schema.org/True), Data::Identifier->new(wd => 'Q16751793'));
32             my @falses = (BOOL_FALSE, qw(https://schema.org/False http://schema.org/False), Data::Identifier->new(wd => 'Q5432619'));
33              
34             foreach my $id (@truths, @falses) {
35             $id = Data::Identifier->new(from => $id)->register;
36             }
37              
38             my $_DEFAULT_INSTANCE = __PACKAGE__->new;
39              
40             my %_4plus12_prefix = (
41             sni => (0<<15)|(0<<14),
42             sid => (0<<15)|(1<<14),
43             hdi => (1<<15)|(0<<14),
44             udi => (1<<15)|(1<<14),
45             );
46              
47             my $_logical = '5e80c7b7-215e-4154-b310-a5387045c336';
48              
49             my %_raes_to_raen = (
50             NONE => 0,
51             NOENT => 2,
52             NOSYS => 6,
53             NOTSUP => 7,
54             NOMEM => 12,
55             INVAL => 13,
56             FAULT => 18,
57             IO => 19,
58             NODATA => 25,
59             NOSPC => 38,
60             TYPEMM => 39,
61             RO => 45,
62             ILLSEQ => 56,
63             BADEXEC => 79,
64             BADFH => 83,
65             );
66             my %_logicals_to_sni = (
67             sni => 10,
68             sid => 115,
69             raen => 116,
70             chat0w => 118,
71             uuid => 119,
72             uri => 121,
73             asciicp => 122,
74             oid => 120,
75             wd => 123,
76             logical => 129,
77             false => 189,
78             true => 190,
79             );
80             my %_logicals_to_sid = (
81             asi => 1,
82             tagname => 3,
83             SEEK_SET => 34,
84             SEEK_CUR => 35,
85             SEEK_END => 36,
86             backwards => 43,
87             forwards => 44,
88             black => 61,
89             white => 62,
90             grey => 63,
91             red => 119,
92             green => 120,
93             blue => 121,
94             cyan => 122,
95             magenta => 123,
96             yellow => 124,
97             orange => 125,
98             gtin => 160,
99             left => 192,
100             right => 193,
101             up => 194,
102             down => 195,
103             north => 208,
104             east => 209,
105             south => 210,
106             west => 211,
107             );
108              
109             my %_wk = (
110             bunit_ns => {uuid => 'e8e9846a-37ec-42fd-8e89-d15f5467aa9c', displayname => 'unit-namespace'},
111             bunit_gen => {uuid => 'b1620795-b29a-4aea-ba46-371b187d0a4b', displayname => 'unit-generator'},
112             dunit_ns => {uuid => 'da8a7fe4-935c-4bf7-9bd1-aaf8fc39305b', displayname => 'derived-unit-namespace'},
113             dunit_gen => {uuid => 'c3446cff-672b-4247-b62c-755a295ee15f', displayname => 'derived-unit-generator'},
114             #x => {uuid => '', displayname => ''},
115             );
116              
117             foreach my $value (values %_wk) {
118             my $uuid = delete $value->{uuid};
119             $value = Data::Identifier->new(uuid => $uuid, %{$value})->register;
120             }
121              
122             my %_base_units = (
123             map {
124             $_->{id} = Data::Identifier::Generate->generic(
125             namespace => $_wk{bunit_ns},
126             generator => $_wk{bunit_gen},
127             style => 'name-based',
128             request => $_->{symbol},
129             displayname => $_->{name},
130             )->register;
131             $_->{symbol} => $_
132             } (
133             {name => 'second', symbol => 's', dimension => 'T', quantity => 'time', variable => [qw(t)]},
134             {name => 'metre', symbol => 'm', dimension => 'L', quantity => 'length', variable => [qw(l x r)]},
135             {name => 'kilogram', symbol => 'kg', dimension => 'M', quantity => 'mass', variable => [qw(m)]},
136             {name => 'ampere', symbol => 'A', dimension => 'I', quantity => 'electric current', variable => [qw(I i)]},
137             {name => 'kelvin', symbol => 'K', dimension => "\N{U+0398}", quantity => 'thermodynamic temperature', variable => [qw(T)]},
138             {name => 'mole', symbol => 'mol', dimension => 'N', quantity => 'amount of substance', variable => [qw(n)]},
139             {name => 'candela', symbol => 'cd', dimension => 'J', quantity => 'luminous intensity', variable => [qw(Iv)]},
140             )
141             );
142              
143             my %_number_units = map { $_ => Data::Identifier::Generate->integer($_)->register } (2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73); # primes
144              
145             my %_composite_unit_elements = (
146             Hz => {s => -1},
147             N => {s => -2, m => 1, kg => 1},
148             Pa => {s => -2, m => -1, kg => 1},
149             J => {s => -2, m => 2, kg => 1},
150             W => {s => -3, m => 2, kg => 1},
151             C => {s => 1, A => 1},
152             V => {s => -3, m => 2, kg => 1, A => -1},
153             F => {s => 4, m => -2, kg => -1, A => 2},
154             "\N{U+03A9}" => {s => -3, m => 2, kg => 1, A => -2},
155             "\N{U+2126}" => {s => -3, m => 2, kg => 1, A => -2}, # alias
156             ohm => {s => -3, m => 2, kg => 1, A => -2}, # alias
157             S => {s => 3, m => -2, kg => -1, A => 2},
158             Wb => {s => -2, m => 2, kg => 1, A => -1},
159             T => {s => -2, kg => 1, A => -1},
160             H => {s => -2, m => 2, kg => 1, A => -2},
161             kat => {s => -1, mol => 1},
162             10 => {2 => 1, 5 => 1},
163             12 => {2 => 2, 3 => 1},
164             24 => {2 => 3, 3 => 1},
165             28 => {2 => 2, 7 => 1},
166             30 => {2 => 1, 3 => 1, 5 => 1},
167             60 => {2 => 2, 3 => 1, 5 => 1},
168             365 => { 5 => 1, 73 => 1},
169             366 => {2 => 1, 3 => 1, 61 => 1},
170             3600 => {2 => 4, 3 => 2, 5 => 2},
171             86400 => {2 => 7, 3 => 3, 5 => 2},
172             );
173              
174             my %_component_to_name = (
175             # Units:
176             (map {$_base_units{$_}{id}->uuid => $_} keys %_base_units),
177             # Numbers:
178             (map {$_number_units{$_}->uuid => $_} keys %_number_units),
179             );
180              
181             my %_si_prefix = (
182             quetta => 30, Q => 30,
183             ronna => 27, R => 27,
184             yotta => 24, Y => 24,
185             zetta => 21, Z => 21,
186             exa => 18, E => 18,
187             peta => 15, P => 15,
188             tera => 12, T => 12,
189             giga => 9, G => 9,
190             mega => 6, M => 6,
191             kilo => 3, k => 3,
192             hecto => 2, h => 2,
193             deca => 1, da => 1,
194             deci => -1, d => -1,
195             centi => -2, c => -2,
196             milli => -3, m => -3,
197 2     2   1875 micro => -6, "\N{GREEK SMALL LETTER MU}" => -6,
  2         18466  
  2         12  
198             nano => -9, n => -9,
199             pico => -12, p => -12,
200             femto => -15, f => -15,
201             atto => -18, a => -18,
202             zepto => -21, z => -21,
203             yocto => -24, y => -24,
204             ronto => -27, r => -27,
205             quecto => -30, q => -30,
206             );
207              
208              
209             sub new {
210 3     3 1 501 my ($pkg, @opts) = @_;
211              
212 3 50       8 croak 'Stray options passed' if scalar @opts;
213              
214 3         9 return bless {}, $pkg;
215             }
216              
217              
218             sub from_bool {
219 0     0 1 0 my ($self, $bool, @opts) = _normalise_args(@_);
220              
221 0 0       0 croak 'Stray options passed' if scalar @opts;
222              
223 0 0       0 return $bool ? BOOL_TRUE : BOOL_FALSE;
224             }
225              
226              
227             sub is_true {
228 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
229              
230 0 0       0 croak 'Stray options passed' if scalar @opts;
231              
232 0         0 foreach my $ref (@truths) {
233 0 0       0 return !!1 if $ref->eq($identifier);
234             }
235              
236 0         0 return !!0;
237             }
238              
239              
240             sub is_false {
241 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
242              
243 0 0       0 croak 'Stray options passed' if scalar @opts;
244              
245 0         0 foreach my $ref (@falses) {
246 0 0       0 return !!1 if $ref->eq($identifier);
247             }
248              
249 0         0 return !!0;
250             }
251              
252              
253             sub pack {
254 9     9 1 3329 my ($self, $template, $identifier, @opts) = _normalise_args(@_);
255 9         19 my $pack_template;
256             my $v;
257              
258 9 50       22 croak 'Stray options passed' if scalar @opts;
259              
260 9   50     19 $template //= '';
261 9 50       13 $identifier = Data::Identifier->new(from => $identifier) unless eval {$identifier->isa('Data::Identifier')};
  9         40  
262              
263 9 100       72 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    50          
    0          
264 6         21 my $bits = int($2);
265              
266 6         24 $v = $identifier->as(Data::Identifier->new(wellknown => $1), no_defaults => 1);
267              
268 6 100       22 if ($bits == 8) {
    100          
    50          
269 2         6 $pack_template = 'C';
270             } elsif ($bits == 16) {
271 2         5 $pack_template = 'n';
272             } elsif ($bits == 32) {
273 2         4 $pack_template = 'N';
274             } else {
275 0         0 croak 'Invalid width: '.$bits;
276             }
277             } elsif ($template eq '4+12') {
278 0         0 my $prefix;
279              
280 0         0 foreach my $type (qw(sid sni hdi udi)) {
281 0         0 $v = $identifier->as(Data::Identifier->new(wellknown => $type), no_defaults => 1, default => undef);
282 0 0       0 if (defined $v) {
283 0 0 0     0 if ($v < 0 || $v > 0x0FFF) {
284 0         0 next;
285             }
286 0         0 $v |= $_4plus12_prefix{$type};
287 0         0 $pack_template = 'n';
288 0         0 last;
289             }
290             }
291             } elsif ($template eq 'uuid128') {
292 1         5 return pack('H*', $identifier->uuid(no_defaults => 1) =~ tr/-//dr);
293             } elsif ($template eq 'uuidhexdash') {
294 1         6 return $identifier->uuid(no_defaults => 1);
295             } elsif ($template eq 'uuidHEXDASH') {
296 1         6 return $identifier->uuid(no_defaults => 1) =~ tr/a-f/A-F/r;
297             } elsif ($template eq 'Data::Identifier') {
298 0         0 return $identifier;
299             }
300              
301 6 50 33     23 if (defined($v) && defined($pack_template)) {
302 6         8 my ($min, $max);
303              
304 6 100       19 if ($pack_template eq 'C') {
    100          
    50          
305 2         5 ($min, $max) = (0, 0xFF);
306             } elsif ($pack_template eq 'n') {
307 2         4 ($min, $max) = (0, 0xFFFF);
308             } elsif ($pack_template eq 'N') {
309 2         5 ($min, $max) = (0, 0xFFFF_FFFF);
310             }
311              
312 6 50 33     36 if ((defined($min) && $v < $min) || (defined($max) && $v > $max)) {
      33        
      33        
313 0         0 croak 'Identifier not in range for '.$template.': '.$v;
314             }
315              
316 6         50 return pack($pack_template, $v);
317             }
318              
319 0         0 croak 'Unknown template: '.$template;
320             }
321              
322              
323             sub unpack {
324 9     9 1 30 my ($self, $template, $data, @opts) = _normalise_args(@_);
325 9         23 my $pack_template;
326             my $type;
327              
328 9 50       23 croak 'Stray options passed' if scalar @opts;
329              
330 9 100       74 if ($template =~ /^(sid|sni|hdi|udi)([1-9][0-9]*)$/) {
    50          
    100          
    100          
    50          
    0          
331 6         20 my $bits = int($2);
332 6         31 $type = $1;
333              
334 6 100       21 if ($bits == 8) {
    100          
    50          
335 2         6 $pack_template = 'C';
336             } elsif ($bits == 16) {
337 2         4 $pack_template = 'n';
338             } elsif ($bits == 32) {
339 2         5 $pack_template = 'N';
340             } else {
341 0         0 croak 'Invalid width: '.$bits;
342             }
343             } elsif ($template eq '4+12') {
344 0         0 my $v;
345             my $prefix;
346              
347 0 0       0 croak 'Input has bad length, expected 2 bytes, got '.length($data) unless length($data) == 2;
348              
349 0         0 $v = unpack('n', $data);
350 0         0 $prefix = $v & 0xF000;
351 0         0 $v = $v & 0x0FFF;
352              
353 0         0 foreach my $key (keys %_4plus12_prefix) {
354 0 0       0 if ($prefix == $_4plus12_prefix{$key}) {
355 0         0 return Data::Identifier->new($key => $v);
356             }
357             }
358              
359 0         0 croak sprintf('Invalid/unknown prefix: 0x%04x', $prefix);
360             } elsif ($template eq 'uuid128') {
361 1 50       5 croak 'Input has bad length, expected 16 bytes, got '.length($data) unless length($data) == 16;
362 1         13 return Data::Identifier->new(uuid => join('-', unpack('H8H4H4H4H12', $data)));
363             } elsif ($template eq 'uuidhexdash') {
364 1         7 return Data::Identifier->new(uuid => $data);
365             } elsif ($template eq 'uuidHEXDASH') {
366 1         6 return Data::Identifier->new(uuid => $data);
367             } elsif ($template eq 'Data::Identifier') {
368             # We don't care too much here if it is actually a Data::Identifier or just any other supported type.
369             # Data::Identifier will do the right thing anyway.
370 0         0 return Data::Identifier->new(from => $data);
371             }
372              
373 6 50 33     27 if (defined($type) && defined($pack_template)) {
374 6         8 my $len = length($data);
375 6         9 my $exp;
376              
377 6 100       20 if ($pack_template eq 'C') {
    100          
    50          
378 2         3 $exp = 1;
379             } elsif ($pack_template eq 'n') {
380 2         5 $exp = 2;
381             } elsif ($pack_template eq 'N') {
382 2         4 $exp = 4;
383             }
384              
385 6 50       15 croak 'Input has bad length, expected '.$exp.' bytes, got '.$len unless $len == $exp;
386              
387 6         34 return Data::Identifier->new($type => unpack($pack_template, $data));
388             }
389              
390 0         0 croak 'Unknown template: '.$template;
391             }
392              
393              
394             sub parse_sirtx {
395 16     16 1 3393 my ($self, $data, @opts) = _normalise_args(@_);
396              
397 16 50       37 croak 'Stray options passed' if scalar @opts;
398              
399 16         46 $self->_load_well_known;
400              
401 16         72 $data =~ s/^\[(.+)\]$/$1/;
402              
403             # Experimental:
404 16 100       54 if (my ($d, $v) = $data =~ /^(\[.+?\]):(.+)$/) {
405 1         8 $d = $self->parse_sirtx($d);
406 1         7 $v =~ s/^\[(.+)\]$/$1/;
407 1         4 return Data::Identifier->new($d => $v);
408             }
409              
410 15 100       136 if ($data =~ /^'([0-9]*)$/) {
    50          
    100          
    100          
    50          
    50          
    100          
    100          
    50          
    100          
    100          
    50          
    50          
411 2   50     13 my $num = int($1 || '0');
412 2         15 require Data::Identifier::Generate;
413 2         11 return Data::Identifier::Generate->integer($num);
414             } elsif ($data =~ /^\/([0-9]+)$/) {
415 0         0 return Data::Identifier->new('d73b6550-5309-46ad-acc9-865c9261065b' => int($1));
416             } elsif ($data =~ /^(sid|sni):([0-9]+)$/) {
417 4         46 return Data::Identifier->new($1 => int($2));
418             } elsif ($data =~ /^uuid:([0-9a-fA-F-]+)$/) {
419 2         12 return Data::Identifier->new(uuid => $1);
420             } elsif ($data =~ /^wd:([QPL][1-9][0-9]*)$/) {
421 0         0 return Data::Identifier->new(wd => $1);
422             } elsif ($data =~ /^~([0-9]+)$/) {
423 0         0 return Data::Identifier->new(hdi => int($1));
424             } elsif ($data =~ /^raen:([0-9]+)$/) {
425 1         8 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => int($1));
426             } elsif ($data =~ /^chat0w:([0-9]+)$/) {
427 1         8 return Data::Identifier->new('2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a' => int($1));
428             } elsif ($data =~ /^asciicp:([0-9]+)$/) {
429 0         0 require Data::Identifier::Generate;
430 0         0 return Data::Identifier::Generate->unicode_character(ascii => int($1));
431             } elsif ($data =~ /^raes:(.+)/) {
432 1 50       7 if (defined(my $raen = $_raes_to_raen{$1})) {
433 1         6 return Data::Identifier->new('2bffc55d-7380-454e-bd53-c5acd525d692' => $raen);
434             }
435             } elsif (defined $_logicals_to_sni{$data}) {
436 2         8 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
437             } elsif (defined $_logicals_to_sid{$data}) {
438 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
439             } elsif ($data =~ /^logical:(.+)$/) {
440 2         8 $data = $1;
441 2 50       6 if (defined $_logicals_to_sni{$data}) {
    0          
442 2         12 return Data::Identifier->new(sni => $_logicals_to_sni{$data});
443             } elsif (defined $_logicals_to_sid{$data}) {
444 0         0 return Data::Identifier->new(sid => $_logicals_to_sid{$data});
445             }
446             }
447              
448 0         0 croak 'Unsupported/invalid SIRTX identifier';
449             }
450              
451              
452             sub render_sirtx {
453 0     0 1 0 my ($self, $identifier, @opts) = _normalise_args(@_);
454 0         0 state $map = [
455             [sid => Data::Identifier->new(wellknown => 'sid')->register],
456             [sni => Data::Identifier->new(wellknown => 'sni')->register],
457             [wd => Data::Identifier->new(wellknown => 'wd')->register],
458             ['/' => Data::Identifier->new(uuid => 'd73b6550-5309-46ad-acc9-865c9261065b')->register],
459             [raen => Data::Identifier->new(uuid => '2bffc55d-7380-454e-bd53-c5acd525d692')->register],
460             [chat0w => Data::Identifier->new(uuid => '2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a')->register],
461             ['~' => Data::Identifier->new(wellknown => 'hdi')->register],
462             ];
463              
464 0 0       0 croak 'Stray options passed' if scalar @opts;
465              
466 0         0 $identifier = Data::Identifier->new(from => $identifier);
467              
468 0 0 0     0 if (defined(my Data::Identifier $generator = $identifier->generator(default => undef)) && defined(my $req = $identifier->request(default => undef))) {
469 0 0 0     0 if ($generator->eq('53863a15-68d4-448d-bd69-a9b19289a191')) {
    0          
470 0         0 return sprintf('\'%u', $req);
471             } elsif ($generator->eq('d74f8c35-bcb8-465c-9a77-01010e8ed25c') && $req =~ /^[Uu]\+([0-9a-fA-F]{4,6})$/) {
472 0         0 my $cp = hex $1;
473 0 0       0 if ($cp < 0x80) {
474 0         0 return sprintf('asciicp:%u', $cp);
475             }
476             }
477             }
478              
479 0         0 foreach my $ent (@{$map}) {
  0         0  
480 0   0     0 my $v = $identifier->as($ent->[1], no_defaults => 1, default => undef) // next;
481 0 0       0 $v = sprintf($ent->[0] =~ /^[a-z]/ ? '%s:%s' : '%s%s', $ent->[0], $v);
482 0 0       0 $v = '['.$v.']' if $v =~ /-/;
483 0         0 return $v;
484             }
485              
486             # Fallback:
487 0         0 return sprintf('[uuid:%s]', $identifier->uuid);
488             }
489              
490             # Too experimental for listing in public API.
491             # TODO: Get this on track for the public API!
492             sub parse_unit_request {
493 0     0 0 0 my ($self, $template, $request, %opts) = _normalise_args(@_);
494 0   0     0 my $exponentas = delete($opts{exponentas}) // 'int';
495 0         0 my @res;
496              
497 0 0       0 croak 'Stray options passed' if scalar keys %opts;
498              
499 0 0       0 if ($template eq 'request') {
500 0         0 foreach my $subreq (split(/--/, $request)) {
501 0         0 my ($uuid, $neg, $exp_mul) = $subreq =~ /^([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})~(-?)([0-9])\z/;
502 0         0 my $exp;
503              
504             #printf("%-42s -> <%s> <%1s> <%s>\n", $subreq, $uuid, $neg, $exp_mul);
505              
506 0 0       0 if ($exponentas eq 'int') {
507 0         0 $exp = int($exp_mul);
508 0 0       0 $exp = -$exp if $neg eq '-';
509             } else {
510 0         0 croak 'Invalid exponentas: '.$exponentas;
511             }
512              
513 0         0 push(@res, {
514             component => Data::Identifier->new(uuid => $uuid),
515             exponent => $exp,
516             });
517             }
518             } else {
519 0         0 croak 'Unknown template: '.$template;
520             }
521              
522 0         0 return \@res;
523             }
524              
525             # Too experimental for listing in public API.
526             # TODO: Get this on track for the public API!
527             sub render_unit_request {
528 0     0 0 0 my ($pkg, $template, $request, %opts) = _normalise_args(@_);
529 0         0 my $displayname = delete $opts{displayname};
530 0         0 my $input;
531             my %took;
532              
533 0 0       0 croak 'Stray options passed' if scalar keys %opts;
534              
535 0 0       0 if (ref($request) eq 'ARRAY') {
536 0   0     0 $request = {map {($_component_to_name{$_->{component}->uuid} // croak 'Unknown base unit: '.$_->{component}->uuid) => $_->{exponent}} @{$request}};
  0         0  
  0         0  
537             }
538              
539 0 0       0 if (defined(my $prefix = delete($request->{prefix}))) {
540 0   0     0 $prefix = $_si_prefix{$prefix} // croak 'Bad prefix: '.$prefix;
541 0   0     0 $request->{10} //= 0;
542 0         0 $request->{10} += $prefix;
543             }
544              
545 0         0 foreach my $key (keys(%_composite_unit_elements)) {
546 0         0 my $n = $_composite_unit_elements{$key};
547 0   0     0 my $mul = delete($request->{$key}) // 0;
548 0 0       0 croak 'Bad exponent: '.$mul if $mul != int($mul);
549 0         0 $mul = int($mul);
550 0 0       0 next if $mul == 0;
551 0         0 foreach my $prime_element (keys %{$n}) {
  0         0  
552 0   0     0 $request->{$prime_element} //= 0;
553 0         0 $request->{$prime_element} += $n->{$prime_element} * $mul;
554             }
555             }
556              
557 0         0 foreach my $key (keys(%_base_units), keys(%_number_units)) {
558 0   0     0 my $mul = delete($request->{$key}) // 0;
559 0 0       0 croak 'Bad exponent: '.$mul if $mul != int($mul);
560 0         0 $mul = int($mul);
561 0 0       0 $took{$key} = $mul if $mul != 0;
562             }
563              
564             {
565 0         0 my @keys = keys %{$request};
  0         0  
  0         0  
566 0 0       0 croak 'Bad extra units: '.join(', ', @keys) if scalar @keys;
567             }
568              
569 0 0       0 unless (scalar(grep {!defined $_number_units{$_}} keys %took)) {
  0         0  
570 0         0 my $i = 1;
571              
572 0         0 foreach my $key (keys %took) {
573 0         0 $i *= $key ** $took{$key};
574             }
575              
576 0 0       0 if ($i == int($i)) {
577 0         0 return $pkg->pack($template => Data::Identifier::Generate->integer($i));
578             }
579 0         0 croak 'Invalid numeric only request';
580             }
581              
582             # Rename from units to UUIDs as keys.
583 0         0 foreach my $key (keys %took) {
584 0   0     0 my $uuid = ($_number_units{$key} // $_base_units{$key}{id})->uuid;
585 0         0 $took{$uuid} = delete $took{$key};
586             }
587              
588 0 0       0 if (scalar(keys %took) == 1) {
589 0         0 my ($mul) = values %took;
590 0 0       0 if ($mul == 1) {
591 0         0 my ($uuid) = keys %took;
592 0         0 return $pkg->pack($template => Data::Identifier->new(uuid => $uuid));
593             }
594             }
595              
596 0         0 $input = join('--', map{$_.'~'.$took{$_}} sort keys %took);
  0         0  
597              
598 0 0       0 if ($template eq 'request') {
599 0         0 return $input;
600             }
601              
602             $opts{namespace} //= $_wk{dunit_ns},
603             $opts{generator} //= $_wk{dunit_gen},
604 0   0     0 $opts{input} = $input;
      0        
605 0         0 $opts{request} = $input;
606 0         0 $opts{displayname} = $displayname;
607              
608 0         0 return $pkg->pack($template => Data::Identifier::Generate->generic(%opts));
609             }
610              
611              
612             sub register_namespace {
613 0     0 1 0 my ($self, $identifier, %opts) = _register_base(@_);
614              
615 0         0 $identifier->uuid; # ensure we map to an UUID
616              
617 0 0       0 croak 'Stray options passed' if scalar keys %opts;
618              
619 0         0 return $identifier->register;
620             }
621              
622              
623             sub register_generator {
624 0     0 1 0 my ($self, $identifier, %opts) = _register_base(@_);
625              
626 0         0 delete $opts{$_} foreach qw(namespace style type);
627 0         0 delete $opts{$_} foreach qw(native_case source_role up_relation for_type copy_tagnames native_ise_template);
628              
629 0 0       0 croak 'Stray options passed' if scalar keys %opts;
630              
631 0         0 return $identifier->register;
632             }
633              
634              
635             sub register_type {
636 0     0 1 0 my ($self, $identifier, %opts) = _register_base(@_);
637 0         0 my $uuid = $identifier->uuid; # ensure we map to an UUID
638              
639 0 0       0 if (defined(my $validate = delete $opts{validate})) {
640 0   0     0 $identifier->{validate} //= $validate;
641             }
642              
643 0 0       0 if (defined(my $namespace = delete $opts{namespace})) {
644 0   0     0 $identifier->{namespace} //= $self->register_namespace($namespace);
645             }
646              
647 0 0       0 if (defined(my $null_value = delete $opts{null_value})) {
648 0         0 my $null = Data::Identifier->new(sid => 0);
649              
650 0   0     0 $null->{id_cache}{$uuid} //= $null_value;
651              
652 0         0 $null->register;
653             }
654              
655 0 0       0 croak 'Stray options passed' if scalar keys %opts;
656              
657 0         0 return $identifier->register;
658             }
659              
660             # ---- Private helpers ----
661              
662             sub _normalise_args {
663 34     34   128 my (@args) = @_;
664              
665 34 50       99 if (scalar(@args)) {
666 34 50 33     105 if (ref($args[0]) && eval {$args[0]->isa(__PACKAGE__)}) {
  34 0       213  
667             # no-op
668             } elsif ($args[0] eq __PACKAGE__) {
669 0         0 $args[0] = $_DEFAULT_INSTANCE;
670             } else {
671 0         0 unshift(@args, $_DEFAULT_INSTANCE);
672             }
673              
674 34         117 return @args;
675             }
676              
677 0         0 return ($_DEFAULT_INSTANCE);
678             }
679              
680             sub _load_well_known {
681 16     16   24 state $done = do {
682 1         6 my %meta = (
683             sni => \%_logicals_to_sni,
684             sid => \%_logicals_to_sid,
685             );
686              
687 1         4 foreach my $type (keys %meta) {
688 2         4 my $hash = $meta{$type};
689              
690 2         2 foreach my $key (keys %{$hash}) {
  2         12  
691 38         58 my $id = Data::Identifier->new($type => $hash->{$key});
692              
693 38 100       49 next unless defined $id->uuid(no_defaults => 1, default => undef);
694              
695 16   50     21 $id->{id_cache} //= {};
696 16   33     46 $id->{id_cache}->{$_logical} //= $key;
697              
698 16         20 $id->register;
699             }
700             }
701             };
702             }
703              
704             sub _update_tag {
705 4     4   31 my ($identifier, $sid, $sni, $tagname) = @_;
706 4   50     29 my $id_cache = $identifier->{id_cache} //= {};
707              
708 4 50 33     21 $id_cache->{Data::Identifier::WK_SID()} //= $sid if defined $sid;
709 4 50 33     15 $id_cache->{Data::Identifier::WK_SNI()} //= $sni if defined $sni;
710              
711 4 50       7 if (defined $tagname) {
712 4         15 my %tagnames = map {$_ => undef} $tagname, $identifier->tagname(list => 1, default => [], no_defaults => 1);
  4         13  
713 4         11 $identifier->{tagname} = [keys %tagnames];
714             }
715              
716 4         13 $identifier->register; # re-register
717              
718 4         6 return $identifier;
719             }
720              
721             # prepares arguments for register_*().
722             # Removes any common arguments from %opts
723             # does NOT call $identifier->register as register_*() might make additional changes that register needs to know about,
724             # so doing it here would just mean to do it twice.
725             sub _register_base {
726 0     0     my ($self, $identifier, %opts) = _normalise_args(@_);
727              
728 0           $identifier = Data::Identifier->new(from => $identifier);
729              
730 0 0         if (defined(my $displayname = delete $opts{displayname})) {
731 0   0       $identifier->{displayname} //= $displayname;
732             }
733              
734 0 0         if (defined(my $tagname = delete $opts{tagname})) {
735 0 0         my %tagnames = map {$_ => undef} (ref $tagname ? @{$tagname} : $tagname), $identifier->tagname(list => 1, default => [], no_defaults => 1);
  0            
  0            
736 0           $identifier->{tagname} = [keys %tagnames];
737             }
738              
739 0           return ($self, $identifier, %opts);
740             }
741              
742             1;
743              
744             __END__