File Coverage

lib/Data/Identifier.pm
Criterion Covered Total %
statement 219 394 55.5
branch 112 292 38.3
condition 74 192 38.5
subroutine 25 35 71.4
pod 21 21 100.0
total 451 934 48.2


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             # Copyright (c) 2023-2025 Philipp Schafft
4              
5             # licensed under Artistic License 2.0 (see LICENSE file)
6              
7             # ABSTRACT: format independent identifier object
8              
9              
10             package Data::Identifier;
11              
12 7     7   2000274 use v5.20;
  7         32  
13 7     7   40 use strict;
  7         13  
  7         224  
14 7     7   63 use warnings;
  7         13  
  7         523  
15              
16 7     7   43 use parent qw(Data::Identifier::Interface::Known Data::Identifier::Interface::Userdata);
  7         14  
  7         66  
17              
18 7     7   547 use Carp;
  7         14  
  7         497  
19 7     7   13554 use Math::BigInt lib => 'GMP';
  7         510100  
  7         97  
20 7     7   302668 use URI;
  7         57481  
  7         3872  
21              
22             our $VERSION = v0.28;
23              
24             use constant {
25 7         1759 RE_UUID => qr/^[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/,
26             RE_OID => qr/^[0-2](?:\.(?:0|[1-9][0-9]*))+$/,
27             RE_URI => qr/^[a-zA-Z][a-zA-Z0-9\+\.\-]+:/,
28             RE_UINT => qr/^(?:0|[1-9][0-9]*)$/,
29             RE_SINT => qr/^(?:0|-?[1-9][0-9]*)$/,
30             RE_QID => qr/^[QPL][1-9][0-9]*$/,
31             RE_DOI => qr/^10\.[1-9][0-9]+(?:\.[0-9]+)*\/./,
32             RE_GTIN => qr/^[0-9]{8}(?:[0-9]{4,6})?$/,
33             RE_UNICODE => qr/^U\+([0-9A-F]{4,7})$/,
34             RE_SIMPLE_TAG => qr/^[^\p{upper case}\s]+$/,
35 7     7   79 };
  7         13  
36              
37             use constant {
38 7         88040 WK_NULL => '00000000-0000-0000-0000-000000000000', # NULL, undef, ...
39             WK_UUID => '8be115d2-dc2f-4a98-91e1-a6e3075cbc31', # uuid
40             WK_OID => 'd08dc905-bbf6-4183-b219-67723c3c8374', # oid
41             WK_URI => 'a8d1637d-af19-49e9-9ef8-6bc1fbcf6439', # uri
42             WK_SID => 'f87a38cb-fd13-4e15-866c-e49901adbec5', # small-identifier
43             WK_WD => 'ce7aae1e-a210-4214-926a-0ebca56d77e3', # wikidata-identifier
44             WK_GTIN => '82d529be-0f00-4b4f-a43f-4a22de5f5312', # gtin
45             WK_IBAN => 'b1418262-6bc9-459c-b4b0-a054d77db0ea', # iban
46             WK_BIC => 'c8a3a132-f160-473c-b5f3-26a748f37e62', # bic
47             WK_DOI => '931f155e-5a24-499b-9fbb-ed4efefe27fe', # doi
48             WK_FC => 'd576b9d1-47d4-43ae-b7ec-bbea1fe009ba', # factgrid-identifier
49             WK_UNICODE_CP => '5f167223-cc9c-4b2f-9928-9fe1b253b560', # unicode-code-point
50             WK_SNI => '039e0bb7-5dd3-40ee-a98c-596ff6cce405', # sirtx-numerical-identifier
51             WK_HDI => 'f8eb04ef-3b8a-402c-ad7c-1e6814cb1998', # host-defined-identifier
52             WK_UDI => '05af99f9-4578-4b79-aabe-946d8e6f5888', # user-defined-identifier
53             WK_CHAT0W => '2c7e15ed-aa2f-4e2f-9a1d-64df0c85875a', # chat-0-word-identifier
54              
55             NS_WD => '9e10aca7-4a99-43ac-9368-6cbfa43636df', # Wikidata-namespace
56             NS_FC => '6491f7a9-0b29-4ef1-992c-3681cea18182', # factgrid-namespace
57             NS_INT => '5dd8ddbb-13a8-4d6c-9264-36e6dd6f9c99', # integer-namespace
58             NS_DATE => 'fc43fbba-b959-4882-b4c8-90a288b7d416', # gregorian-date-namespace
59             NS_GTIN => 'd95d8b1f-5091-4642-a6b0-a585313915f1', # gtin-namespace
60             NS_UNICODE_CP => '132aa723-a373-48bf-a88d-69f1e00f00cf', # 'unicode-character-namespace'
61 7     7   52 };
  7         15  
62              
63             # Features:
64             my $enabled_oid = 1;
65              
66             my %uuid_to_uriid_org = (
67             WK_UUID() => 'uuid',
68             WK_OID() => 'oid',
69             WK_URI() => 'uri',
70             WK_SID() => 'sid',
71             WK_GTIN() => 'gtin',
72             WK_WD() => 'wikidata-identifier',
73             );
74              
75             my %uuid_org_to_uuid = map {$uuid_to_uriid_org{$_} => $_} keys %uuid_to_uriid_org;
76              
77             my $well_known_uuid = __PACKAGE__->new(ise => WK_UUID, validate => RE_UUID);
78              
79             my %well_known = (
80             uuid => $well_known_uuid,
81             oid => __PACKAGE__->new($well_known_uuid => WK_OID, validate => RE_OID),
82             uri => __PACKAGE__->new($well_known_uuid => WK_URI, validate => RE_URI),
83             sid => __PACKAGE__->new($well_known_uuid => WK_SID, validate => RE_UINT),
84             sni => __PACKAGE__->new($well_known_uuid => WK_SNI, validate => RE_UINT),
85             wd => __PACKAGE__->new($well_known_uuid => WK_WD, validate => RE_QID, namespace => NS_WD, generate => 'id-based'),
86             fc => __PACKAGE__->new($well_known_uuid => WK_FC, validate => RE_QID, namespace => NS_FC, generate => 'id-based'),
87             gtin => __PACKAGE__->new($well_known_uuid => WK_GTIN, validate => RE_GTIN, namespace => NS_GTIN, generate => 'id-based'),
88             iban => __PACKAGE__->new($well_known_uuid => WK_IBAN),
89             bic => __PACKAGE__->new($well_known_uuid => WK_BIC),
90             doi => __PACKAGE__->new($well_known_uuid => WK_DOI, validate => RE_DOI),
91              
92             # Unofficial, not part of public API:
93             # Also used by Data::Identifier::Util!
94             unicodecp => __PACKAGE__->new($well_known_uuid => WK_UNICODE_CP, validate => RE_UNICODE, namespace => NS_UNICODE_CP, generate => 'id-based'),
95              
96             hdi => __PACKAGE__->new($well_known_uuid => WK_HDI, validate => RE_UINT),
97             udi => __PACKAGE__->new($well_known_uuid => WK_UDI, validate => RE_UINT),
98             null => __PACKAGE__->new($well_known_uuid => WK_NULL),
99             );
100              
101             my %registered;
102              
103             $_->register foreach values %well_known;
104              
105             # Refill with sids:
106             {
107             my %wk_sids = (
108             WK_NULL() => 0, # NULL
109             'ddd60c5c-2934-404f-8f2d-fcb4da88b633' => 1, # also-shares-identifier
110             WK_UUID() => 2,
111             'bfae7574-3dae-425d-89b1-9c087c140c23' => 3, # tagname
112             '7f265548-81dc-4280-9550-1bd0aa4bf748' => 4, # has-type
113             WK_URI() => 5,
114             WK_OID() => 6,
115             # Unassigned: 7
116             'd0a4c6e2-ce2f-4d4c-b079-60065ac681f1' => 8, # language-tag-identifier
117             WK_WD() => 9,
118             '923b43ae-a50e-4db3-8655-ed931d0dd6d4' => 10, # specialises
119             'eacbf914-52cf-4192-a42c-8ecd27c85ee1' => 11, # unicode-string
120             '928d02b0-7143-4ec9-b5ac-9554f02d3fb1' => 12, # integer
121             'dea3782c-6bcb-4ce9-8a39-f8dab399d75d' => 13, # unsigned-integer
122             # Unassigned: 14, 15
123             '6ba648c2-3657-47c2-8541-9b73c3a9b2b4' => 16, # default-context
124             '52a516d0-25d8-47c7-a6ba-80983e576c54' => 17, # proto-file
125             '1cd4a6c6-0d7c-48d1-81e7-4e8d41fdb45d' => 18, # final-file-size
126             '6085f87e-4797-4bb2-b23d-85ff7edc1da0' => 19, # text-fragment
127             '4c9656eb-c130-42b7-9348-a1fee3f42050' => 20, # also-list-contains-also
128             '298ef373-9731-491d-824d-b2836250e865' => 21, # proto-message
129             '7be4d8c7-6a75-44cc-94f7-c87433307b26' => 22, # proto-entity
130             '65bb36f2-b558-48af-8512-bca9150cca85' => 23, # proxy-type
131             'a1c478b5-0a85-4b5b-96da-d250db14a67c' => 24, # flagged-as
132             '59cfe520-ba32-48cc-b654-74f7a05779db' => 25, # marked-as
133             '2bffc55d-7380-454e-bd53-c5acd525d692' => 26, # roaraudio-error-number
134             WK_SID() => 27,
135             'd2750351-aed7-4ade-aa80-c32436cc6030' => 28, # also-has-role
136             '11d8962c-0a71-4d00-95ed-fa69182788a8' => 29, # also-has-comment
137             '30710bdb-6418-42fb-96db-2278f3bfa17f' => 30, # also-has-description
138             # Unassigned: 31
139             '448c50a8-c847-4bc7-856e-0db5fea8f23b' => 32, # final-file-encoding
140             '79385945-0963-44aa-880a-bca4a42e9002' => 33, # final-file-hash
141             '3fde5688-6e34-45e9-8f33-68f079b152c8' => 34, # SEEK_SET
142             'bc598c52-642e-465b-b079-e9253cd6f190' => 35, # SEEK_CUR
143             '06aff30f-70e8-48b4-8b20-9194d22fc460' => 36, # SEEK_END
144             '59a5691a-6a19-4051-bc26-8db82c019df3' => 37, # inode
145             WK_CHAT0W() => 112, # chat-0-word-identifier
146             WK_SNI() => 113, # sirtx-numerical-identifier
147             WK_GTIN() => 160,
148             );
149              
150             foreach my $ise (keys %wk_sids) {
151             my $identifier = __PACKAGE__->new(ise => $ise);
152             $identifier->{id_cache} //= {};
153             $identifier->{id_cache}->{WK_SID()} //= $wk_sids{$ise};
154             $identifier->register; # re-register
155             }
156             }
157              
158             # Refill with snis:
159             {
160             my %wk_snis = (
161             WK_NULL() => 0, # NULL
162             '039e0bb7-5dd3-40ee-a98c-596ff6cce405' => 10, # sirtx-numerical-identifier
163             'f87a38cb-fd13-4e15-866c-e49901adbec5' => 115, # small-identifier
164             '2bffc55d-7380-454e-bd53-c5acd525d692' => 116, # roaraudio-error-number
165             WK_CHAT0W() => 118, # chat-0-word-identifier
166             WK_UUID() => 119,
167             WK_OID() => 120,
168             WK_URI() => 121,
169             WK_WD() => 123,
170             );
171              
172             foreach my $ise (keys %wk_snis) {
173             my $identifier = __PACKAGE__->new(ise => $ise);
174             $identifier->{id_cache} //= {};
175             $identifier->{id_cache}->{WK_SNI()} //= $wk_snis{$ise};
176             $identifier->register; # re-register
177             }
178             }
179              
180             # Update NULL:
181             {
182             my $identifier = __PACKAGE__->new(uuid => WK_NULL);
183             $identifier->{id_cache} //= {};
184             foreach my $type (WK_HDI, WK_CHAT0W) {
185             $identifier->{id_cache}->{$type} //= 0;
186             }
187             $identifier->register;
188             }
189              
190             # Some extra tags such as namespaces:
191             foreach my $ise (NS_WD, NS_INT, NS_DATE) {
192             my $identifier = __PACKAGE__->new(ise => $ise);
193             $identifier->register; # re-register
194             }
195              
196             # Refill with displaynames
197             {
198             my %displaynames = (
199             WK_NULL() => 'null',
200             WK_UUID() => 'uuid',
201             WK_OID() => 'oid',
202             WK_URI() => 'uri',
203             WK_SID() => 'small-identifier',
204             WK_WD() => 'wikidata-identifier',
205             WK_GTIN() => 'gtin',
206             WK_IBAN() => 'iban',
207             WK_BIC() => 'bic',
208             WK_DOI() => 'doi',
209             WK_FC() => 'factgrid-identifier',
210             WK_UNICODE_CP() => 'unicode-code-point',
211             WK_SNI() => 'sirtx-numerical-identifier',
212             WK_HDI() => 'host-defined-identifier',
213             WK_UDI() => 'user-defined-identifier',
214             WK_CHAT0W() => 'chat-0-word-identifier',
215             NS_WD() => 'Wikidata-namespace',
216             NS_FC() => 'factgrid-namespace',
217             NS_INT() => 'integer-namespace',
218             NS_DATE() => 'gregorian-date-namespace',
219             NS_UNICODE_CP() => 'unicode-character-namespace',
220              
221             'ddd60c5c-2934-404f-8f2d-fcb4da88b633' => 'also-shares-identifier',
222             'bfae7574-3dae-425d-89b1-9c087c140c23' => 'tagname',
223             '7f265548-81dc-4280-9550-1bd0aa4bf748' => 'has-type',
224             'd0a4c6e2-ce2f-4d4c-b079-60065ac681f1' => 'language-tag-identifier',
225             '923b43ae-a50e-4db3-8655-ed931d0dd6d4' => 'specialises',
226             'eacbf914-52cf-4192-a42c-8ecd27c85ee1' => 'unicode-string',
227             '928d02b0-7143-4ec9-b5ac-9554f02d3fb1' => 'integer',
228             'dea3782c-6bcb-4ce9-8a39-f8dab399d75d' => 'unsigned-integer',
229             '6ba648c2-3657-47c2-8541-9b73c3a9b2b4' => 'default-context',
230             '52a516d0-25d8-47c7-a6ba-80983e576c54' => 'proto-file',
231             '1cd4a6c6-0d7c-48d1-81e7-4e8d41fdb45d' => 'final-file-size',
232             '6085f87e-4797-4bb2-b23d-85ff7edc1da0' => 'text-fragment',
233             '4c9656eb-c130-42b7-9348-a1fee3f42050' => 'also-list-contains-also',
234             '298ef373-9731-491d-824d-b2836250e865' => 'proto-message',
235             '7be4d8c7-6a75-44cc-94f7-c87433307b26' => 'proto-entity',
236             '65bb36f2-b558-48af-8512-bca9150cca85' => 'proxy-type',
237             'a1c478b5-0a85-4b5b-96da-d250db14a67c' => 'flagged-as',
238             '59cfe520-ba32-48cc-b654-74f7a05779db' => 'marked-as',
239             '2bffc55d-7380-454e-bd53-c5acd525d692' => 'roaraudio-error-number',
240             'd2750351-aed7-4ade-aa80-c32436cc6030' => 'also-has-role',
241             '11d8962c-0a71-4d00-95ed-fa69182788a8' => 'also-has-comment',
242             '30710bdb-6418-42fb-96db-2278f3bfa17f' => 'also-has-description',
243             '448c50a8-c847-4bc7-856e-0db5fea8f23b' => 'final-file-encoding',
244             '79385945-0963-44aa-880a-bca4a42e9002' => 'final-file-hash',
245             '3fde5688-6e34-45e9-8f33-68f079b152c8' => 'SEEK_SET',
246             'bc598c52-642e-465b-b079-e9253cd6f190' => 'SEEK_CUR',
247             '06aff30f-70e8-48b4-8b20-9194d22fc460' => 'SEEK_END',
248             '59a5691a-6a19-4051-bc26-8db82c019df3' => 'inode',
249             '53863a15-68d4-448d-bd69-a9b19289a191' => 'unsigned-integer-generator',
250             'e8aa9e01-8d37-4b4b-8899-42ca0a2a906f' => 'signed-integer-generator',
251             'd74f8c35-bcb8-465c-9a77-01010e8ed25c' => 'unicode-character-generator',
252             '55febcc4-6655-4397-ae3d-2353b5856b34' => 'rgb-colour-generator',
253             '97b7f241-e1c5-4f02-ae3c-8e31e501e1dc' => 'date-generator',
254             '19659233-0a22-412c-bdf1-8ee9f8fc4086' => 'multiplicity-generator',
255             '5ec197c3-1406-467c-96c7-4b1a6ec2c5c9' => 'minimum-multiplicity-generator',
256             );
257              
258             foreach my $ise (keys %displaynames) {
259             my $identifier = __PACKAGE__->new(ise => $ise);
260             $identifier->{displayname} //= $displaynames{$ise};
261             $identifier->register; # re-register
262             }
263             }
264              
265             {
266             # ISE -> namespace
267             my %namespaces_uint = (
268             '4a7fc2e2-854b-42ec-b24f-c7fece371865' => 'ac59062c-6ba2-44de-9f54-09e28f2c0b5c', # e621-post-identifier: e621-post-namespace
269             'a0a4fae2-be6f-4a51-8326-6110ba845a16' => '69b7ff38-ca78-43a8-b9ea-66cb02312eef', # e621-pool-identifier: e621-pool-namespace
270             '6e3590b6-2a0c-4850-a71f-8ba196a52280' => 'b96e5d94-0767-40fa-9864-5977eb507ae0', # danbooru2chanjp-post-identifier: danbooru2chanjp-post-namespace
271             );
272             my %namespaces_sint = (
273             '2bffc55d-7380-454e-bd53-c5acd525d692' => '744eaf4e-ae93-44d8-9ab5-744105222da6', # roaraudio-error-number: roaraudio-error-namespace
274             );
275             my %namespaces_simple_tag = (
276             '6fe0dbf0-624b-48b3-b558-0394c14bad6a' => '3623de4d-0dd4-4236-946a-2613467d50f1', # e621tag: e621tag-namespace
277             'c5632c60-5da2-41af-8b60-75810b622756' => '93f2c36b-8cb6-4f2c-924b-98188f224235', # danbooru2chanjp-tag: danbooru2chanjp-tag-namespace
278             );
279              
280             foreach my $ise (keys %namespaces_uint) {
281             my $identifier = __PACKAGE__->new(ise => $ise);
282             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_uint{$ise});
283             $identifier->{validate} //= RE_UINT;
284             $identifier->{generate} //= 'id-based';
285             $identifier->register; # re-register
286             }
287              
288             foreach my $ise (keys %namespaces_sint) {
289             my $identifier = __PACKAGE__->new(ise => $ise);
290             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_sint{$ise});
291             $identifier->{validate} //= RE_SINT;
292             $identifier->{generate} //= 'id-based';
293             $identifier->register; # re-register
294             }
295              
296             foreach my $ise (keys %namespaces_simple_tag) {
297             my $identifier = __PACKAGE__->new(ise => $ise);
298             $identifier->{namespace} //= __PACKAGE__->new(ise => $namespaces_simple_tag{$ise});
299             $identifier->{validate} //= RE_SIMPLE_TAG;
300             $identifier->{generate} //= 'id-based';
301             $identifier->register; # re-register
302             }
303              
304             # validate => RE_QID, namespace => NS_FC, generate => 'id-based'
305             }
306              
307             # Call this after after we loaded all our stuff and before anyone else will register stuff:
308             __PACKAGE__->_known_provider('wellknown');
309              
310              
311             sub new {
312 1113     1113 1 11605 my ($pkg, $type, $id, %opts) = @_;
313 1113         2044 my $self = bless {};
314              
315 1113 50       2198 croak 'No type given' unless defined $type;
316 1113 50       1941 croak 'No id given' unless defined $id;
317              
318 1113 100 100     4618 if (!ref($type) && $type eq 'from') {
319 55 50       105 if (ref($id)) {
320 0         0 my $from = $id;
321 0 0 0     0 if ($id->isa('Data::Identifier')) {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
322 0 0       0 if (scalar(keys %opts)) {
323 0         0 $type = $id->type;
324 0         0 $id = $id->id;
325             } else {
326 0         0 return $id;
327             }
328             } elsif ($id->isa('URI')) {
329 0         0 $type = 'uri';
330             } elsif ($id->isa('Mojo::URL')) {
331 0         0 $type = 'uri';
332 0         0 $id = $id->to_string;
333             } elsif ($id->isa('Data::URIID::Result')) {
334 0   0 0   0 $opts{displayname} //= sub { return $from->attribute('displayname', default => undef) };
  0         0  
335 0         0 $type = $id->id_type;
336 0         0 $id = $id->id;
337             } elsif ($id->isa('Data::URIID::Base') || $id->isa('Data::URIID::Colour') || $id->isa('Data::URIID::Service')) {
338             #$opts{displayname} //= $id->name if $id->isa('Data::URIID::Service');
339 0   0     0 $opts{displayname} //= $id->displayname(default => undef, no_defaults => 1);
340 0         0 $type = 'ise';
341 0         0 $id = $id->ise;
342             } elsif ($id->isa('Data::TagDB::Tag')) {
343 0   0 0   0 $opts{displayname} //= sub { $from->displayname };
  0         0  
344 0         0 $type = 'ise';
345 0         0 $id = $id->ise;
346             } elsif ($id->isa('File::FStore::File') || $id->isa('File::FStore::Adder') || $id->isa('File::FStore::Base')) {
347 0         0 $type = 'ise';
348 0         0 $id = $id->contentise;
349             } elsif ($id->isa('SIRTX::Datecode')) {
350 0         0 $id = $id->as('Data::Identifier');
351 0 0       0 unless (scalar(keys %opts)) {
352 0         0 return $id->as('Data::Identifier');
353             }
354 0         0 $type = $id->type;
355 0         0 $id = $id->id;
356             } elsif ($id->isa('Business::ISBN')) {
357 0         0 $type = $well_known{gtin};
358 0         0 $id = $id->as_isbn13->as_string([]);
359             } elsif ($id->isa('Data::Identifier::Interface::Simple')) {
360             # TODO: We cannot call $id->as('Data::Identifier') here as much as that would be fun,
361             # as this might in turn call exactly this code again resulting in a deep recursion.
362             # A future version might come up with some trick here.
363 0         0 $type = 'ise';
364 0         0 $id = $id->ise;
365             } else {
366 0         0 croak 'Unsupported input data';
367             }
368             } else {
369             # If it's not a ref, try as ise.
370 55         71 $type = 'ise';
371             }
372             }
373              
374 1113 100 100     3356 if (!ref($type) && $type eq 'ise') {
375 881 50       1519 croak 'Undefined identifier but type is ISE' unless defined $id;
376              
377 881 50       5071 if ($id =~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/) { # allow less normalised form than RE_UUID
    0          
    0          
378 881         1296 $type = $well_known_uuid;
379              
380             # For bootstrap only.
381 881 100 66     2138 if (!defined($type) && $id eq '8be115d2-dc2f-4a98-91e1-a6e3075cbc31') {
382 7         197 $self->{type} = $well_known_uuid = $type = $self;
383 7         27 $self->{id} = $id;
384             }
385             } elsif ($id =~ RE_OID) {
386 0         0 $type = 'oid';
387             } elsif ($id =~ RE_URI) {
388 0         0 $type = 'uri';
389             } else {
390 0         0 croak 'Not a valid ISE identifier';
391             }
392             }
393              
394 1113 100       2097 unless (ref $type) {
395 133 100       417 if ($type =~ /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/) { # allow less normalised form than RE_UUID
    100          
396 6         14 $type = $pkg->new(uuid => $type);
397 6         12 $type->register;
398             } elsif ($type eq 'wellknown') {
399 8         19 $self = $well_known{$id};
400 8 50       20 croak 'Unknown well-known' unless defined $self;
401 8         27 return $self;
402             } else {
403 119         227 $type = $well_known{$type};
404             }
405 125 50       252 croak 'Unknown type name' unless defined $type;
406             }
407              
408 1105 50       3257 croak 'Not a valid type' unless $type->isa(__PACKAGE__);
409              
410             # we normalise URIs first as they may then normalised again
411 1105 100 100     3466 if ($type == ($well_known{uri} // 0)) {
412 3         8 my $uri = $id.''; # force stringification
413              
414 3 100       44 if ($uri =~ m#^urn:uuid:([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})$#) {
    50          
    50          
    50          
    50          
415 1         2 $id = $1;
416 1         2 $type = $well_known_uuid;
417             } elsif ($uri =~ m#^urn:oid:([0-2](?:\.(?:0|[1-9][0-9]*))+)$#) {
418 0         0 $id = $1;
419 0         0 $type = $well_known{oid};
420             } elsif ($uri =~ m#^https?://www\.wikidata\.org/entity/([QPL][1-9][0-9]*)$#) {
421 0         0 $id = $1;
422 0         0 $type = $well_known{wd};
423             } elsif ($uri =~ m#^https?://doi\.org/(10\..+)$#) {
424 0         0 $id = $1;
425 0         0 $type = $well_known{doi};
426             } elsif ($uri =~ m#^https?://uriid\.org/([^/]+)/[^/]+#) {
427 2         9 my $ptype = $1;
428 2 50 66     19 if (defined($uuid_org_to_uuid{$ptype}) || $ptype =~ RE_UUID) {
429 2         14 my $u = URI->new($uri);
430 2         13260 my @path_segments = $u->path_segments;
431 2 50 33     213 if (scalar(@path_segments) == 3 && $path_segments[0] eq '') {
432 2   66     23 $type = $pkg->new(uuid => ($uuid_org_to_uuid{$path_segments[1]} // $path_segments[1]));
433 2         21 $id = $path_segments[2];
434             }
435             }
436             }
437             }
438              
439 1105 100 50     2651 if ($type == ($well_known_uuid // 0)) {
    100 50        
440 1025         1967 $id = lc($id); # normalise
441             } elsif ($type == ($well_known{oid} // 0)) {
442 1 50       7 if ($id =~ /^2\.25\.([1-9][0-9]*)$/) {
443 1         7 my $hex = Math::BigInt->new($1)->as_hex;
444 1         809 $hex =~ s/^0x//;
445 1         6 $hex = ('0' x (32 - length($hex))) . $hex;
446 1         16 $hex =~ s/^(.{8})(.{4})(.{4})(.{4})(.{12})$/$1-$2-$3-$4-$5/;
447 1         3 $type = $well_known_uuid;
448 1         3 $id = $hex;
449             }
450             }
451              
452 1105 100       3235 if (defined(my $v = $registered{$type->uuid}{$id})) {
453 552         1617 return $v;
454             }
455              
456              
457 553 100       1205 if (defined $type->{validate}) {
458 544 50       6260 croak 'Identifier did not validate against type' unless $id =~ $type->{validate};
459             }
460              
461 553         1343 $self->{type} = $type;
462 553         952 $self->{id} = $id;
463              
464 553         1005 foreach my $key (qw(validate namespace generator request generate displayname)) {
465 3318 100       6146 next unless defined $opts{$key};
466 164   66     554 $self->{$key} //= $opts{$key};
467             }
468              
469 553         759 foreach my $key (qw(namespace generator)) {
470 1106 100       2447 if (defined(my $v = $self->{$key})) {
471 31 50       75 unless (ref $v) {
472 31         114 $self->{$key} = $pkg->new(from => $v);
473             }
474             }
475             }
476              
477 553         1739 return bless $self;
478             }
479              
480              
481             #@returns __PACKAGE__
482             sub random {
483 0     0 1 0 my ($pkg, %opts) = @_;
484 0   0     0 my $type = $opts{type} // 'uuid';
485              
486 0 0       0 if (ref $type) {
487 0 0       0 if ($type == $well_known_uuid) {
488 0         0 $type = 'uuid';
489             } else {
490 0         0 croak 'Invalid/Unsupported type';
491             }
492             }
493              
494 0 0 0     0 if ($type ne 'ise' && $type ne 'uuid') {
495 0         0 croak 'Invalid/Unsupported type';
496             }
497              
498 0         0 require Data::Identifier::Generate;
499 0         0 my $uuid = Data::Identifier::Generate->_random(%opts{'sources'});
500 0         0 return $pkg->new(uuid => $uuid, %opts{'displayname'});
501             }
502              
503              
504              
505             #@deprecated
506             sub wellknown {
507 0     0 1 0 my ($pkg, @args) = @_;
508 0         0 return $pkg->known('wellknown', @args);
509             }
510              
511              
512             #@returns __PACKAGE__
513             sub type {
514 46     46 1 2958 my ($self) = @_;
515 46         118 return $self->{type};
516             }
517              
518              
519              
520             sub id {
521 25     25 1 25 my ($self) = @_;
522 25         64 return $self->{id};
523             }
524              
525              
526             sub uuid {
527 7208     7208 1 23998 my ($self, %opts) = @_;
528              
529 7208 50 100     38660 return $self->{id_cache}{WK_UUID()} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{WK_UUID()});
      66        
530              
531 7208 100       13935 if ($self->{type} == $well_known_uuid) {
532 7181         30213 return $self->{id};
533             }
534              
535 27 100       47 unless ($opts{no_defaults}) {
536             # Try to generate a UUID and recheck cache:
537 3         8 $self->_generate;
538 3 50 33     22 return $self->{id_cache}{WK_UUID()} if defined($self->{id_cache}) && defined($self->{id_cache}{WK_UUID()});
539             }
540              
541 24 50       96 return $opts{default} if exists $opts{default};
542 0         0 croak 'Identifier has no valid UUID';
543             }
544              
545             sub oid {
546 916     916 1 1593 my ($self, %opts) = @_;
547 916         1284 my $type = $well_known{oid};
548              
549 916 100 66     4723 return $self->{id_cache}{WK_OID()} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{WK_OID()});
      100        
550              
551 428 50       912 if ($self->{type} == $type) {
552 0         0 return $self->{id};
553             }
554              
555 428 50       892 unless ($opts{no_defaults}) {
556 428 50       786 if (defined(my $uuid = $self->uuid(default => undef))) {
557 428         2544 return $self->{id_cache}{WK_OID()} = sprintf('2.25.%s', Math::BigInt->new('0x'.$uuid =~ tr/-//dr));
558             }
559             }
560              
561 0 0       0 return $opts{default} if exists $opts{default};
562 0         0 croak 'Identifier has no valid OID';
563             }
564              
565             sub uri {
566 917     917 1 1940 my ($self, %opts) = @_;
567 917         1376 my $type = $well_known{uri};
568              
569 917 100 66     5735 if (!$opts{no_defaults} && !defined($opts{style}) && defined($self->{id_cache}) && defined($self->{id_cache}{WK_URI()})) {
      100        
      100        
570 488         1355 return $self->{id_cache}{WK_URI()};
571             }
572              
573 429 50       978 if ($self->{type} == $type) {
574 0         0 return $self->{id};
575             }
576              
577 429   100     1883 $opts{style} //= 'urn';
578              
579 429 50       772 unless ($opts{no_defaults}) {
580 429 50 66     1828 if ($self->{type} == $well_known{wd}) {
    50 33        
    100 33        
    50          
581 0         0 return $self->{id_cache}{WK_URI()} = sprintf('http://www.wikidata.org/entity/%s', $self->{id});
582             } elsif ($self->{type} == $well_known{doi}) {
583 0         0 return $self->{id_cache}{WK_URI()} = sprintf('https://doi.org/%s', $self->{id});
584             } elsif (defined(my $uuid = $self->uuid(default => undef)) && $opts{style} eq 'urn') {
585 428         1940 return $self->{id_cache}{WK_URI()} = sprintf('urn:uuid:%s', $uuid);
586             } elsif ($enabled_oid && defined(my $oid = $self->oid(default => undef)) && $opts{style} eq 'urn') {
587 0         0 return $self->{id_cache}{WK_URI()} = sprintf('urn:oid:%s', $oid);
588             } else {
589 1         886 my $u = URI->new("https://uriid.org/");
590 1         13899 my $type_uuid = $self->{type}->uuid;
591 1   33     13 $u->path_segments('', $uuid_to_uriid_org{$type_uuid} // $type_uuid, $self->{id});
592 1         218 return $self->{id_cache}{WK_URI()} = $u;
593             }
594             }
595              
596 0 0       0 return $opts{default} if exists $opts{default};
597 0         0 croak 'Identifier has no valid URI';
598             }
599              
600             sub sid {
601 919     919 1 1712 my ($self, %opts) = @_;
602 919         1300 my $type = $well_known{sid};
603 919 100 66     3906 return $self->{id_cache}{WK_SID()} if defined($self->{id_cache}) && defined($self->{id_cache}{WK_SID()});
604 294 50       764 if ($self->{type} == $type) {
605 0         0 return $self->{id};
606             }
607              
608 294 50       1102 return $opts{default} if exists $opts{default};
609 0         0 croak 'Identifier has no valid SID';
610             }
611              
612              
613              
614             sub ise {
615 22     22 1 43 my ($self, %opts) = @_;
616 22         41 my $type = $self->{type};
617 22         35 my $have_default = exists $opts{default};
618 22         83 my $default = delete $opts{default};
619 22         28 my $value;
620              
621 22 50 33     88 if ($type == $well_known{uuid} || $type == $well_known{oid} || $type == $well_known{uri}) {
      33        
622 22         43 $value = $self->{id};
623             } else {
624 0         0 $opts{default} = undef;
625 0   0     0 $value = $self->uuid(%opts) // $self->oid(%opts) // $self->uri(%opts);
      0        
626             }
627              
628 22 50       112 return $value if defined $value;
629 0 0       0 return $default if $have_default;
630 0         0 croak 'Identifier has no valid ISE';
631             }
632              
633              
634             sub as {
635 128     128 1 245 my ($self, $as, %opts) = @_;
636              
637 128 100 66     252 $as = $opts{rawtype} if $as eq 'raw' && defined($opts{rawtype});
638              
639 128 100 66     143 if (ref($as) && eval {$as->isa(__PACKAGE__)}) {
  6         16  
640 6         11 my $type_uuid = $as->uuid;
641 6         9 my $next_type;
642              
643 6 50       16 return $self->id if $self->type->eq($as);
644              
645 6         11 foreach my $test (qw(uuid oid uri sid)) {
646 24 100       38 if ($as == $well_known{$test}) {
647 3         3 $next_type = $test;
648 3         3 last;
649             }
650             }
651              
652 6 100       11 if (defined $next_type) {
653 3 0 33     9 return $self->{id_cache}{$type_uuid} if !$opts{no_defaults} && defined($self->{id_cache}) && defined($self->{id_cache}{$type_uuid});
      33        
654 3         7 $as = $next_type;
655             } else {
656 3 50 33     26 return $self->{id_cache}{$type_uuid} if defined($self->{id_cache}) && defined($self->{id_cache}{$type_uuid});
657 0 0       0 return $opts{default} if exists $opts{default};
658 0         0 croak 'Unknown/Unsupported as: '.$as;
659             }
660             }
661              
662 125 100 66     241 return $self if ($as =~ /^[A-Z]/ || $as =~ /::/) && eval {$self->isa($as)};
  122   66     423  
663              
664 3 50       55 if ($self->isa('Data::Identifier::Interface::Subobjects')) {
665 0         0 require Data::Identifier::Interface::Subobjects; # Is this required?
666 0   0     0 $opts{$_} //= $self->so_get($_, default => undef) foreach Data::Identifier::Interface::Subobjects->KEYS;
667             }
668              
669 3 50       12 if (defined(my $so = $opts{so})) {
670 0         0 require Data::Identifier::Interface::Subobjects; # Is this required?
671 0   0     0 $opts{$_} //= $so->so_get($_, default => undef) foreach Data::Identifier::Interface::Subobjects->KEYS;
672             }
673              
674 3 50       4 $self = __PACKAGE__->new(from => $self) unless eval {$self->isa(__PACKAGE__)};
  3         12  
675              
676 3 50 33     29 if ($as eq 'uuid' || $as eq 'oid' || $as eq 'uri' || $as eq 'sid' || $as eq 'ise') {
    0 33        
    0 33        
    0 33        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
677 3         11 my $func = $self->can($as);
678 3         9 return $self->$func(%opts);
679             } elsif ($as eq __PACKAGE__) {
680 0         0 return $self;
681             } elsif ($as eq 'URI') {
682 0         0 my $had_default = exists $opts{default};
683 0         0 my $default = delete $opts{default};
684 0         0 my $val = $self->uri(%opts, default => undef);
685              
686 0 0       0 return URI->new($val) if defined $val;
687 0 0       0 if ($had_default) {
688 0 0       0 return $default if ref $default;
689 0         0 return URI->new($default);
690             }
691 0         0 croak 'No value for URI';
692             } elsif ($as eq 'Mojo::URL') {
693 0         0 my $had_default = exists $opts{default};
694 0         0 my $default = delete $opts{default};
695 0         0 my $val = $self->uri(%opts, default => undef);
696              
697 0         0 require Mojo::URL;
698              
699 0 0       0 return Mojo::URL->new($val) if defined $val;
700 0 0       0 if ($had_default) {
701 0 0       0 return $default if ref $default;
702 0         0 return Mojo::URL->new($default);
703             }
704 0         0 croak 'No value for URI';
705             } elsif ($as eq 'Data::URIID::Result' && defined($opts{extractor})) {
706 0         0 return $opts{extractor}->lookup($self->type->uuid => $self->id);
707             } elsif ($as eq 'Data::URIID::Service' && defined($opts{extractor})) {
708 0         0 return $opts{extractor}->service($self->uuid);
709             } elsif ($as eq 'SIRTX::Datecode' && eval {
710 0         0 require SIRTX::Datecode;
711 0         0 SIRTX::Datecode->VERSION(v0.03);
712 0         0 1;
713             }) {
714 0         0 return SIRTX::Datecode->new(from => $self);
715             } elsif ($as eq 'Data::URIID::Colour' && eval {
716 0         0 require Data::URIID;
717 0         0 require Data::URIID::Colour;
718 0         0 Data::URIID::Colour->VERSION(v0.14);
719 0         0 1;
720             }) {
721 0         0 return Data::URIID::Colour->new(from => $self, %opts{qw(extractor db fii store)});
722             } elsif ($as eq 'Data::TagDB::Tag' && defined($opts{db})) {
723 0 0       0 if ($opts{autocreate}) {
724 0         0 return $opts{db}->create_tag($self);
725             } else {
726 0         0 return $opts{db}->tag_by_id($self);
727             }
728             } elsif ($as eq 'File::FStore::File' && defined($opts{store})) {
729 0         0 return scalar($opts{store}->query(ise => $self));
730             } elsif ($as eq 'Business::ISBN' && $self->type->eq('gtin')) {
731 0         0 require Business::ISBN;
732 0         0 my $val = Business::ISBN->new($self->id);
733 0 0 0     0 return $val if defined($val) && $val->is_valid;
734             }
735              
736 0 0       0 return $opts{default} if exists $opts{default};
737 0         0 croak 'Unknown/Unsupported as: '.$as;
738             }
739              
740              
741             sub eq {
742 41     41 1 64 my ($self, $other) = @_;
743              
744 41         48 foreach my $e ($self, $other) {
745 82 100 66     120 if (defined($e) && !scalar(eval {$e->isa(__PACKAGE__)})) {
  82         222  
746 4 50       26 if (defined $well_known{$e}) {
747 0         0 $e = $well_known{$e}
748             } else {
749 4         8 $e = Data::Identifier->new(from => $e);
750             }
751             }
752             }
753              
754 41 50       54 if (defined($self)) {
755 41 50       50 return undef unless defined $other;
756 41 100       138 return 1 if $self == $other;
757 11 50       17 return undef unless $self->type->eq($other->type);
758 11         34 return $self->id eq $other->id;
759             } else {
760 0         0 return !defined($other);
761             }
762             }
763              
764              
765             sub cmp {
766 0     0 1 0 my ($self, $other) = @_;
767              
768 0         0 foreach my $e ($self, $other) {
769 0 0 0     0 if (defined($e) && !scalar(eval {$e->isa(__PACKAGE__)})) {
  0         0  
770 0 0       0 if (defined $well_known{$e}) {
771 0         0 $e = $well_known{$e}
772             } else {
773 0         0 $e = Data::Identifier->new(from => $e);
774             }
775             }
776             }
777              
778 0 0       0 if (defined($self)) {
779 0 0       0 return undef unless defined $other;
780 0 0       0 return 0 if $self == $other;
781 0 0       0 if ((my $r = $self->type->cmp($other->type)) != 0) {
782 0         0 return $r;
783             }
784              
785             {
786 0         0 my $self_id = $self->id;
  0         0  
787 0         0 my $other_id = $other->id;
788              
789 0 0 0     0 if ((my ($sa, $sb) = $self_id =~ /^([^0-9]*)([0-9]+)$/) && (my ($oa, $ob) = $other_id =~ /^([^0-9]*)([0-9]+)$/)) {
790 0         0 my $r = $sa cmp $oa;
791 0 0       0 return $r if $r;
792 0         0 return $sb <=> $ob;
793             }
794              
795 0         0 return $self_id cmp $other_id;
796             }
797             } else {
798 0         0 return !defined($other);
799             }
800             }
801              
802              
803             #@returns __PACKAGE__
804             sub namespace {
805 3     3 1 4 my ($self, %opts) = @_;
806 3         44 my $has_default = exists $opts{default};
807 3         4 my $default = delete $opts{default};
808              
809 3         8 delete $opts{no_defaults};
810              
811 3 50       5 croak 'Stray options passed' if scalar keys %opts;
812              
813 3 50       12 return $self->{namespace} if defined $self->{namespace};
814              
815 0 0       0 return $default if $has_default;
816              
817 0         0 croak 'No namespace';
818             }
819              
820              
821             #@returns __PACKAGE__
822             sub generator {
823 0     0 1 0 my ($self, %opts) = @_;
824 0         0 my $has_default = exists $opts{default};
825 0         0 my $default = delete $opts{default};
826              
827 0         0 delete $opts{no_defaults};
828              
829 0 0       0 croak 'Stray options passed' if scalar keys %opts;
830              
831 0 0       0 return $self->{generator} if defined $self->{generator};
832              
833 0 0       0 return $default if $has_default;
834              
835 0         0 croak 'No generator';
836             }
837              
838              
839             sub request {
840 0     0 1 0 my ($self, %opts) = @_;
841 0         0 my $has_default = exists $opts{default};
842 0         0 my $default = delete $opts{default};
843              
844 0         0 delete $opts{no_defaults};
845              
846 0 0       0 croak 'Stray options passed' if scalar keys %opts;
847              
848 0 0       0 return $self->{request} if defined $self->{request};
849              
850 0 0       0 return $default if $has_default;
851              
852 0         0 croak 'No request';
853             }
854              
855              
856             #@returns __PACKAGE__
857             sub register {
858 909     909 1 1473 my ($self) = @_;
859 909         1738 $registered{$self->{type}->uuid}{$self->{id}} = $self;
860              
861 909         1543 foreach my $type_name (qw(uuid oid uri sid)) {
862 3636   50     10365 my $f = $self->can($type_name) || next;
863 3636   100     6030 my $v = $self->$f(default => undef) // next;
864 3342         362672 $registered{$well_known{$type_name}->uuid}{$v} = $self;
865             }
866              
867 909         1401 foreach my $extra (WK_SNI()) {
868 909   100     2010 my $v = $self->{id_cache}{$extra} // next;
869 154         385 $registered{$extra}{$v} = $self;
870             }
871              
872 909         1809 return $self;
873             }
874              
875              
876              
877             sub displayname {
878 13     13 1 33 my ($self, %opts) = @_;
879              
880 13 50       47 if (defined(my $displayname = $self->{displayname})) {
881 13 50       27 $displayname = $self->$displayname() if ref $displayname;
882              
883             # recheck and return as any of the above conversions could result in $displayname becoming invalid.
884 13 50 33     98 return $displayname if defined($displayname) && length($displayname);
885             }
886              
887 0 0       0 return $self->id.'' unless $opts{no_defaults}; # force stringification.
888 0 0       0 return $opts{default} if exists $opts{default};
889 0         0 croak 'No value for displayname';
890             }
891              
892              
893 0     0 1 0 sub displaycolour { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
894 0     0 1 0 sub icontext { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
895 0     0 1 0 sub description { my ($self, %opts) = @_; return $opts{default}; }
  0         0  
896              
897             # ---- Private helpers ----
898              
899             sub import {
900 15     15   1044 my ($pkg, $opts) = @_;
901 15 50       8683 return unless defined $opts;
902 0 0       0 croak 'Bad options' unless ref($opts) eq 'HASH';
903              
904 0 0       0 if (defined(my $disable = $opts->{disable})) {
905 0 0       0 $disable = [split /\s*,\s*/, $disable] unless ref $disable;
906 0         0 foreach my $to_disable (@{$disable}) {
  0         0  
907 0 0       0 if ($to_disable eq 'oid') {
908 0         0 $enabled_oid = undef;
909 0         0 undef *oid;
910             } else {
911 0         0 croak 'Unknown feature: '.$to_disable;
912             }
913             }
914             }
915             }
916              
917             sub _generate {
918 3     3   4 my ($self) = @_;
919 3 50       8 unless (exists $self->{_generate}) {
920 3         6 my __PACKAGE__ $type = $self->type;
921              
922 3 50       8 if (defined(my $generate = $type->{generate})) {
923 3 50       4 unless (ref $generate) {
924 3         12 $self->{generate} = $generate = {style => $generate};
925             }
926              
927 3   50     12 $self->{id_cache} //= {};
928              
929 3 50       3 if (defined(my __PACKAGE__ $ns = eval {$type->namespace->uuid})) {
  3         7  
930 3         4 my $style = $generate->{style};
931 3         3 my $input;
932              
933 3 50       5 if ($style eq 'id-based') {
934 3         6 $input = lc($self->id);
935             } else {
936 0         0 croak 'Unsupported generator style';
937             }
938              
939 3 50       5 if (defined $input) {
940 3         16 require Data::Identifier::Generate;
941 3         18 $self->{id_cache}{WK_UUID()} = Data::Identifier::Generate->_uuid_v5($ns, $input);
942             }
943             }
944             }
945             }
946 3         8 $self->{_generate} = undef;
947             }
948              
949             sub _known_provider {
950 9     9   34 my ($pkg, $class, %opts) = @_;
951 9 50       48 croak 'Unsupported options passed' if scalar(keys %opts);
952              
953 9 100 33     84 if ($class eq 'wellknown') {
    50          
954 8         18 state $wellknown = do {
955 7         41 my %hash = map{$_ => $_} values(%well_known), map {values %{$_}} values(%registered);
  1708         3664  
  35         49  
  35         681  
956 7         369 [values %hash];
957             };
958              
959 8         32 return ($wellknown, rawtype => __PACKAGE__);
960             } elsif ($class eq 'registered' || $class eq ':all') {
961 1         6 my %hash = map{$_ => $_} values(%well_known), map {values %{$_}} values(%registered);
  244         452  
  5         7  
  5         85  
962 1         48 return ([values %hash], rawtype => __PACKAGE__);
963             }
964              
965 0           croak 'Unsupported class';
966             }
967              
968             1;
969              
970             __END__