File Coverage

blib/lib/Data/UUID/NCName.pm
Criterion Covered Total %
statement 89 104 85.5
branch 16 36 44.4
condition 10 20 50.0
subroutine 22 23 95.6
pod 6 6 100.0
total 143 189 75.6


line stmt bran cond sub pod time code
1             package Data::UUID::NCName;
2              
3 2     2   70697 use 5.012;
  2         8  
4 2     2   10 use strict;
  2         5  
  2         49  
5 2     2   10 use warnings FATAL => 'all';
  2         4  
  2         66  
6 2     2   10 use feature 'state';
  2         4  
  2         217  
7 2     2   13 use base 'Exporter::Tiny';
  2         5  
  2         1034  
8 2     2   7912 use overload;
  2         971  
  2         15  
9              
10 2     2   1044 use MIME::Base32 ();
  2         1963  
  2         47  
11 2     2   488 use MIME::Base64 ();
  2         731  
  2         39  
12 2     2   13 use Carp ();
  2         4  
  2         39  
13              
14 2     2   1272 use Type::Params qw(compile multisig);
  2         171155  
  2         24  
15 2     2   736 use Types::Standard qw(slurpy Maybe Any Item Str Int Dict Object Optional);
  2         5  
  2         9  
16 2     2   2783 use Type::Library -base, -declare => qw(Stringable AnyUUID Format Radix Ver);
  2         18  
  2         12  
17 2     2   3084 use Type::Utils -all;
  2         10124  
  2         22  
18              
19             sub _to_string {
20 0     0   0 my $x = shift;
21 0 0 0     0 overload::Method($x, '""') || $x->can('to_string') || $x->can('as_string');
22             }
23              
24             declare Stringable, as Object, where \&_to_string;
25              
26             declare AnyUUID, as Str|Stringable, where {
27 2     2   7604 use bytes;
  2         17  
  2         17  
28             my $x = ref $_ ? _to_string($_)->($_) : $_;
29              
30             return 1 if length $x == 16;
31              
32             if (my ($hex) = ($x =~ /^\s*(?i:urn:uuid:)?([0-9A-Fa-f-]{32,})\s*$/sm)) {
33             $hex =~ s/-//g;
34             return 1 if length $hex == 32;
35             }
36              
37             if (my ($b64) = ($x =~ m!^\s*([0-9A-Za-z+/_-]=*)\s*$!sm)) {
38             $b64 =~ tr!-_!+/!;
39             return 1 if 16 == length(MIME::Base64::decode($b64));
40             }
41              
42             return;
43             };
44              
45             enum Format, [qw(str hex b64 bin)];
46             enum Radix, [32, 64];
47             enum Ver, [0, 1]; # there may be more versions later on
48              
49             =encoding utf8
50              
51             =head1 NAME
52              
53             Data::UUID::NCName - Make valid NCName tokens which are also UUIDs
54              
55             =head1 VERSION
56              
57             Version 0.05
58              
59             =cut
60              
61             our $VERSION = '0.05';
62              
63             =head1 SYNOPSIS
64              
65             use Data::UUID::NCName qw(:all);
66              
67             my $uuid = '1ff916f3-6ed7-443a-bef5-f4c85f18cd10';
68             my $ncn = to_ncname($uuid, version => 1);
69             my $ncn32 = to_ncname($uuid, version => 1, radix => 32);
70              
71             # $ncn is now "EH_kW827XQ6719MhfGM0QL".
72             # $ncn32 is "Ed74rn43o25b255puzbprrtiql" and case-insensitive.
73              
74             # from Test::More, this will output 'ok':
75             is(from_ncname($ncn, version => 1),
76             $uuid, 'Decoding result matches original');
77              
78             =head1 DESCRIPTION
79              
80             The purpose of this module is to devise an alternative representation
81             of the L which conforms to
82             the constraints of various other identifiers such as NCName, and create an
83             L mapping between
84             them.
85              
86             =head1 FORMAT DEPRECATION NOTICE
87              
88             After careful consideration, I have decided to change the UUID-NCName
89             format in a minor yet incompatible way. In particular, I have moved
90             the quartet containing the
91             L|https://tools.ietf.org/html/rfc4122#section-4.1.1> to the
92             very end of the identifier, whereas it previously was mixed into the
93             middle somewhere.
94              
95             This can be considered an application of L
96             Law|https://en.wikipedia.org/wiki/Postel%27s_law>, based on the
97             assumption that these identifiers will be generated through other
98             methods, and potentially naïvely. Like the C field, the
99             C field has a limited acceptable range of values. If, for
100             example, one were to attempt to generate a conforming identifier by
101             simply generating a random Base32 or Base64 string, it will be
102             difficult to ensure that the C field will indeed conform when
103             the identifier is converted to a standard UUID. By moving the
104             C field out to the end of the identifier, everything between
105             the C and C bookends can be generated randomly
106             without any further consideration, like so:
107              
108             our @B64_ALPHA = ('A'..'Z', 'a'..'z', 0..9, qw(- _));
109              
110             sub make_cheapo_b64_uuid_ncname () {
111             my @vals = map { int rand 64 } (1..20); # generate content
112             push @vals, 8 + int rand 4; # last digit is special
113             'E' . join '', map { $B64_ALPHA[$_] } @vals; # 'E' for UUID V4
114             }
115              
116             # voilà:
117             my $cheap = make_cheapo_b64_uuid_ncname;
118             # EPrakcT1o2arqWSOuIMGSK or something
119              
120             # as expected, we can decode it (version 1, naturally)
121             my $uu = Data::UUID::NCName::from_ncname($cheap, version => 1);
122             # 3eb6a471-3d68-4d9a-aaea-5923ae20c192 - UUID is valid
123              
124             Furthermore, since the default behaviour is to align the bits of the
125             last byte to the size of the encoding symbol, and since the C
126             bits are masked, a compliant RFC4122 UUID will I end with
127             C, C, C, or C, in I Base32 (case-insensitive) and
128             Base64 variants.
129              
130             Since I have already released this module prior to this format change,
131             I have added a C parameter to both L and
132             L. The version currently defaults to C<0>, the old one,
133             but will issue a warning if not explicitly set. Later I will change
134             the default to C<1>, while keeping the warning, then later still,
135             finally remove the warning with C<1> as the default. This should
136             ensure that any code written during the transition produces the
137             correct results.
138              
139             =over 4
140              
141             Unless you have to support identifiers generated from version 0.04 or
142             older, B 1>>.
143              
144             =back
145              
146             =head1 RATIONALE & METHOD
147              
148             The UUID is a generic identifier which is large enough to be globally
149             unique. This makes it useful as a canonical name for data objects in
150             distributed systems, especially those that cross administrative
151             jurisdictions, such as the World-Wide Web. The
152             L,
153             however, of the UUID, precludes it from being used in many places
154             where it would be useful to do so.
155              
156             In particular, there are grammars for many types of identifiers which
157             must not begin with a digit. Others are case-insensitive, or
158             prohibited from containing hyphens (present in both the standard
159             notation and Base64URL), or indeed anything outside of
160             C<^[A-Za-z_][0-9A-Za-z_]*$>.
161              
162             The hexadecimal notation of the UUID has a 5/8 chance of beginning
163             with a digit, Base64 has a 5/32 chance, and Base32 has a 3/16
164             chance. As such, the identifier must be modified in such a way as to
165             guarantee beginning with an alphabetic letter (or underscore C<_>, but some
166             grammars even prohibit that, so we omit it as well).
167              
168             While it is conceivable to simply add a padding character, there are a
169             few considerations which make it more appealing to derive the initial
170             character from the content of the UUID itself:
171              
172             =over 4
173              
174             =item *
175              
176             UUIDs are large (128-bit) identifiers as it is, and it is undesirable
177             to add meaningless syntax to them if we can avoid doing so.
178              
179             =item *
180              
181             128 bits is an inconvenient number for aligning to both Base32 (130)
182             and Base64 (132), though 120 divides cleanly into 5, 6 and 8.
183              
184             =item *
185              
186             The 13th quartet, or higher four bits of the
187             C of the UUID is constant, as it indicates
188             the UUID's version. If we encode this value using the scheme common to
189             both Base64 and Base32, we get values between C and C

, with the

190             valid subset between C and C.
191              
192             =back
193              
194             B extract the UUID's version quartet, shift all subsequent
195             data 4 bits to the left, zero-pad to the octet, encode with either
196             I or I, truncate, and finally prepend the encoded
197             version character. VoilE, one token-safe UUID.
198              
199             =head1 APPLICATIONS
200              
201             =over 4
202              
203             =item XML IDs
204              
205             The C production appears to have been constricted, inadvertently
206             or otherwise, from L in both
207             the XML 1.0 and 1.1 specifications, to
208             L by L
209             Part 2|http://www.w3.org/TR/xmlschema-2/#ID>. This removes the colon
210             character C<:> from the grammar. The net effect is that
211              
212            
213              
214             while being a I ID I valid under DTD validation, is
215             I valid per XML Schema Part 2 or anything that uses it
216             (e.g. Relax NG).
217              
218             =item RDF blank node identifiers
219              
220             Blank node identifiers in RDF are intended for serialization, to act
221             as a handle so that multiple RDF statements can refer to the same
222             blank node. The L
223             specifies|http://www.w3.org/TR/rdf-concepts/#section-URI-Vocabulary>
224             that the validity constraints of blank node identifiers be delegated
225             to the concrete syntax specifications. The L
226             specification|http://www.w3.org/TR/rdf-syntax-grammar/#rdf-id>
227             lists the blank node identifier as NCName. However, according to
228             L, this is a
229             valid blank node identifier:
230              
231             _:42df00ec-30a2-431f-be9e-e3a612b325db
232              
233             despite L
234             version|http://www.w3.org/TeamSubmission/turtle/#nodeID> listing a
235             production equivalent to the more conservative NCName. NTriples
236             syntax is L
237             constrained|http://www.w3.org/TR/rdf-testcases/#ntriples>, given as
238             C<^[A-Za-z][0-9A-Za-z]*$>.
239              
240             =item Generated symbols
241              
242             =over 4
243              
244             There are only two hard things in computer science: cache
245             invalidation and naming things [and off-by-one errors].
246              
247             -- Phil Karlton [extension of unknown origin]
248              
249             =back
250              
251             Suppose you wanted to create a L
252             programming|http://en.wikipedia.org/wiki/Literate_programming> system
253             (I do). One of your (my) stipulations is that the symbols get defined
254             in the I, rather than the I. However, you (I) still want
255             to be able to validate the code's syntax, and potentially even run the
256             code, without having to commit to naming anything. You are (I am) also
257             interested in creating a global map of classes, datatypes and code
258             fragments, which can be operated on and tested in isolation, ported to
259             other languages, or transplanted into the more conventional packages
260             of programs, libraries and frameworks. The Base32 UUID NCName
261             representation should be adequate for placeholder symbols in just
262             about any programming language, save for those which do not permit
263             identifiers as long as 26 characters (which are extremely scarce).
264              
265             =back
266              
267             =head1 EXPORT
268              
269             No subroutines are exported by default. Be sure to include at least
270             one of the following in your C statement:
271              
272             =over 4
273              
274             =item :all
275              
276             Import all functions.
277              
278             =item :decode
279              
280             Import decode-only functions.
281              
282             =item :encode
283              
284             Import encode-only functions.
285              
286             =item :32
287              
288             Import base32-only functions.
289              
290             =item :64
291              
292             Import base64-only functions.
293              
294             =back
295              
296             =cut
297              
298             # exporter stuff
299              
300             our %EXPORT_TAGS = (
301             all => [qw(to_ncname from_ncname
302             to_ncname_32 from_ncname_32
303             to_ncname_64 from_ncname_64)],
304             decode => [qw(from_ncname from_ncname_32 from_ncname_64)],
305             encode => [qw(to_ncname to_ncname_32 to_ncname_64)],
306             32 => [qw(to_ncname_32 from_ncname_32)],
307             64 => [qw(to_ncname_64 from_ncname_64)],
308             );
309              
310             # export nothing by default
311             our @EXPORT = ();
312             our @EXPORT_OK = @{$EXPORT_TAGS{all}};
313              
314             # uuid format string, so meta.
315             my $UUF = sprintf('%s-%s-%s-%s-%s', '%02x' x 4, ('%02x' x 2) x 3, '%02x' x 6);
316             # yo dawg i herd u liek format strings so we put a format string in yo
317             # format string
318              
319             # dispatch tables for encoding/decoding
320              
321             my %ENCODE = (
322             32 => sub {
323             my @in = unpack 'C*', shift;
324             my $align = shift;
325             $in[-1] >>= 1 if $align;
326             my $out = MIME::Base32::encode_rfc3548(pack 'C*', @in);
327              
328             # we want lower case because IT IS RUDE TO SHOUT
329             lc substr($out, 0, 25);
330             },
331             64 => sub {
332             my @in = unpack 'C*', shift;
333             my $align = shift;
334             $in[-1] >>= 2 if $align;
335              
336             my $out = MIME::Base64::encode(pack 'C*', @in);
337             # note that the rfc4648 sequence ends in +/ or -_
338             $out =~ tr!+/!-_!;
339              
340             substr($out, 0, 21);
341             },
342             );
343              
344             my %DECODE = (
345             32 => sub {
346             my ($in, $align) = @_;
347              
348             $in = uc substr($in, 0, 25) . '0';
349              
350             my @out = unpack 'C*', MIME::Base32::decode_rfc3548($in);
351             $out[-1] <<= 1 if $align;
352              
353             pack 'C*', @out;
354             },
355             64 => sub {
356             my ($in, $align) = @_;
357              
358             $in = substr($in, 0, 21) . 'A==';
359             # note that the rfc4648 sequence ends in +/ or -_
360             $in =~ tr!-_!+/!;
361              
362             #warn unpack 'H*', MIME::Base64::decode($in);
363              
364             my @out = unpack 'C*', MIME::Base64::decode($in);
365              
366             $out[-1] <<= 2 if $align;
367              
368             pack 'C*', @out;
369             },
370             );
371              
372             my @TRANSFORM = (
373             # old version, prior to format change
374             [
375             # _bin_uuid_to_pair
376             sub {
377             my $data = shift;
378             my @list = unpack 'N4', $data;
379              
380             my $ver = ($list[1] & 0x0000f000) >> 12;
381             $list[1] = ($list[1] & 0xffff0000) |
382             (($list[1] & 0x00000fff) << 4) | ($list[2] >> 28);
383             $list[2] = ($list[2] & 0x0fffffff) << 4 | ($list[3] >> 28);
384             $list[3] <<= 4;
385              
386             return $ver, pack 'N4', @list;
387             },
388             # _pair_to_bin_uuid
389             sub {
390             my ($ver, $data) = @_;
391              
392             $ver &= 0xf;
393              
394             my @list = unpack 'N4', $data;
395              
396             $list[3] >>= 4;
397             $list[3] |= (($list[2] & 0xf) << 28);
398             $list[2] >>= 4;
399             $list[2] |= (($list[1] & 0xf) << 28);
400             $list[1] = ($list[1] & 0xffff0000) | ($ver << 12) |
401             (($list[1] >> 4) & 0xfff);
402              
403             #warn unpack 'H*', pack 'N4', @list;
404              
405             pack 'N4', @list;
406             },
407             ],
408             # new version
409             [
410             # _bin_uuid_to_pair
411             sub {
412             my $data = shift;
413             my @list = unpack 'N4', $data;
414              
415             my $ver = ($list[1] & 0x0000f000) >> 12;
416             my $var = ($list[2] & 0xf0000000) >> 24;
417             $list[1] = ($list[1] & 0xffff0000) |
418             (($list[1] & 0x00000fff) << 4) |
419             (($list[2] & 0x0fffffff) >> 24);
420             $list[2] = ($list[2] & 0x00ffffff) << 8 | ($list[3] >> 24);
421             $list[3] = ($list[3] << 8) | $var;
422              
423             return $ver, pack 'N4', @list;
424             },
425             # _pair_to_bin_uuid
426             sub {
427             my ($ver, $data) = @_;
428              
429             $ver &= 0xf;
430              
431             my @list = unpack 'N4', $data;
432              
433             my $var = ($list[3] & 0xf0) << 24;
434              
435             $list[3] >>= 8;
436             $list[3] |= (($list[2] & 0xff) << 24);
437             $list[2] >>= 8;
438             $list[2] |= (($list[1] & 0xf) << 24) | $var;
439             $list[1] = ($list[1] & 0xffff0000) | ($ver << 12) |
440             (($list[1] >> 4) & 0xfff);
441              
442             #warn unpack 'H*', pack 'N4', @list;
443              
444             pack 'N4', @list;
445             },
446             ],
447             );
448              
449             sub _encode_version {
450 406     406   631 my $ver = $_[0] & 15;
451             # A (0) starts at 65. this should never be higher than F (version
452             # 5) for a valid UUID, but even an invalid one will never be
453             # higher than P (15).
454              
455             # XXX boo-hoo, this will break in EBCDIC.
456 406         1289 chr($ver + 65);
457             }
458              
459             sub _decode_version {
460             # modulo makes sure this always returns between 0 and 15
461 401     401   1104 return((ord(uc $_[0]) - 65) % 16);
462             }
463              
464             =head1 SUBROUTINES
465              
466             =head2 to_ncname $UUID [, $RADIX ] [, %PARAMS ]
467              
468             Turn C<$UUID> into an NCName. The UUID can be in the canonical
469             (hyphenated) hexadecimal form, non-hyphenated hexadecimal, Base64
470             (regular and base64url), or binary. The function returns a legal
471             NCName equivalent to the UUID, in either Base32 or Base64 (url), given
472             a specified C<$RADIX> of 32 or 64. If the radix is omitted, Base64
473             is assumed.
474              
475             The following keyword parameters are also accepted, and override the
476             positional parameters where applicable:
477              
478             =over 4
479              
480             =item radix 32|64
481              
482             Either 32 or 64 to explicitly specify Base32 or Base64 output.
483             Defaults to 64.
484              
485             =item version 0|1
486              
487             Version 0 will generate the original version of NCName identifiers,
488             prior to the changes noted above. Version 1 is the new version, which
489             is I backwards-compatible. The default, for a transitional
490             period, is to generate version 0, but complain about it. Set the
491             version explicitly (to 1, or to 0 if you need backwards compatibility)
492             to eliminate the warning messages.
493              
494             =item align $FALSY|$TRUTHY
495              
496             Align the last 4 bits to the Base32/Base64 symbol size. You almost
497             certainly want this, so the default is I.
498              
499             =back
500              
501             =cut
502              
503             sub to_ncname {
504 406     406 1 110096 state $dict = slurpy Dict[
505             radix => Optional[Radix],
506             version => Optional[Ver],
507             align => Optional[Item],
508             slurpy Any];
509 406         10015 state $check = multisig(
510             [AnyUUID, Optional[Radix], $dict],
511             [AnyUUID, $dict]);
512              
513 406         10123 my ($uuid, $radix, $p) = $check->(@_);
514              
515             # optional radix moved to named parameter
516 406 100       11930 if (ref $radix) {
517 204         317 $p = $radix;
518 204         324 undef $radix;
519             }
520              
521 406   100     1649 $radix //= $p->{radix} || 64;
      66        
522 406   50     1391 $p->{align} = !!($p->{align} // 1);
523              
524 406 50       851 unless (defined $p->{version}) {
525 0         0 Carp::carp('Set an explicit `version` to eliminate this warning.' .
526             ' See Data::UUID::NCName docs.');
527 0         0 $p->{version} = 0;
528             }
529              
530             # type checking has ensured this is Stringable so get the string
531 406 50       713 $uuid = _to_string($uuid)->($uuid) if ref $uuid;
532              
533             # get the uuid into a binary string
534 406         504 my $bin;
535 406 50       811 if (length $uuid == 16) {
536             # this is already a binary string
537 0         0 $bin = $uuid;
538             }
539             else {
540             # get rid of any whitespace
541 406         851 $uuid =~ s/\s+//g;
542              
543             # handle hexadecimal
544 406 50       1405 if ($uuid =~ /^(?i:urn:uuid:)?[0-9A-Fa-f-]{32,}$/) {
    0          
545 406         725 $uuid =~ s/^urn:uuid://i;
546 406         1295 $uuid =~ s/-//g;
547             #warn $uuid;
548 406         1489 $bin = pack 'H*', $uuid;
549             }
550             # handle base64
551             elsif ($uuid =~ m!^[0-9A-Za-z=+/_-]$!) {
552             # canonicalize first
553 0         0 $uuid =~ tr!-_!+/!;
554 0         0 $bin = MIME::Base64::decode($uuid);
555             }
556             else {
557 0         0 Carp::croak("Couldn't figure out what to do with putative UUID.");
558             }
559             }
560              
561             # extract the version
562 406         1060 my ($version, $content) = $TRANSFORM[$p->{version}][0]->($bin);
563              
564             # wah-lah.
565 406         863 _encode_version($version) . $ENCODE{$radix}->($content, $p->{align});
566             }
567              
568             =head2 from_ncname $NCNAME [, $FORMAT [, $RADIX] ] [, %PARAMS ]
569              
570             Turn an appropriate C<$NCNAME> back into a UUID, where I,
571             unless overridden by C<$RADIX>, is defined beginning with one initial
572             alphabetic letter (A to Z, case-insensitive) followed by either:
573              
574             =over 4
575              
576             =item B<25> Base32 characters, or
577              
578             =item B<21> Base64URL characters.
579              
580             =back
581              
582             The function will return C immediately if it cannot match
583             either of these patterns. Input past the 21-character mark (for
584             Base64) or 25-character mark (for Base32) is ignored.
585              
586             This function returns a UUID of type C<$FORMAT>, which if left
587             undefined, must be one of the following:
588              
589             =over 4
590              
591             =item str
592              
593             The canonical UUID format, like so:
594             C<33fcc995-5d10-477e-a9b4-c9cc405bbf04>. This is the default.
595              
596             =item hex
597              
598             The same thing, minus the hyphens.
599              
600             =item b64
601              
602             Base64.
603              
604             =item bin
605              
606             A binary string.
607              
608             =back
609              
610             This function also takes the new keyword-style parameters:
611              
612             =over 4
613              
614             =item format
615              
616             As above.
617              
618             =item radix
619              
620             As above.
621              
622             =item version
623              
624             Sets the identifier version. Defaults to version 0 with a warning. See
625             the note about setting an explicit C parameter in L.
626              
627             =item align
628              
629             Assume the last few bits are aligned to the symbol, as in L.
630              
631             =back
632              
633             =cut
634              
635             my %FORMAT = (
636             str => sub {
637             sprintf $UUF, unpack 'C*', shift;
638             },
639             hex => sub {
640             unpack 'H*', shift;
641             },
642             b64 => sub {
643             my $x = MIME::Base64::encode(shift);
644             $x =~ s/=+$//;
645             $x;
646             },
647             bin => sub {
648             shift;
649             },
650             );
651              
652             sub from_ncname {
653 401     401 1 670 state $dict = slurpy Dict[
654             format => Optional[Format],
655             radix => Optional[Radix],
656             version => Optional[Ver],
657             align => Optional[Item],
658             slurpy Any];
659 401         4922 state $check = multisig(
660             [Str, $dict],
661             [Str, Maybe[Format], Optional[Radix], $dict],
662             [Str, Maybe[Format], $dict],
663             );
664              
665 401         15156 my ($ncname, $format, $radix, $p) = $check->(@_);
666              
667             # handle vagaries of legacy positional parameters
668 401 50       15473 if (ref $format) {
    0          
669 401         645 $p = $format;
670 401         679 undef $format;
671             }
672             elsif (ref $radix) {
673 0         0 $p = $radix;
674 0         0 undef $radix;
675             }
676              
677             # unconditional override by key-value radix and format parameters
678 401 100       1086 $radix = $p->{radix} if defined $p->{radix};
679              
680             # reuse this variable because it doesn't get used for anything else
681 401   33     2061 $format = $FORMAT{$p->{format} || $format || 'str'};
      50        
682              
683             # coerce align parameter to boolish
684 401   50     1459 $p->{align} = !!($p->{align} // 1);
685              
686             # enforce explicit presence of version
687 401 50       825 unless (defined $p->{version}) {
688 0         0 Carp::carp('Set an explicit `version` to eliminate this warning.' .
689             ' See Data::UUID::NCName docs.');
690 0         0 $p->{version} = 0;
691             }
692              
693             # obviously this must be defined
694 401 50       688 return unless defined $ncname;
695              
696             # no whitespace
697 401         3777 $ncname =~ s/^\s*(.*?)\s*$/$1/sm;
698              
699             # note that the rfc4648 sequence ends in +/ or -_
700 401 50       2149 my ($version, $content) = ($ncname =~ /^([A-Za-z])([0-9A-Za-z_-]{21,})$/)
701             or return;
702              
703 401 100       888 if ($radix) {
704 400 50 66     1157 Carp::croak("Radix must be either 32 or 64, not $radix")
705             unless $radix == 32 || $radix == 64;
706             }
707             else {
708             # detect what to do based on input
709 1         3 my $len = length $ncname;
710 1 50       6 if ($ncname =~ m![_-]!) {
    0          
    0          
711             # containing these characters means base64url
712 1         3 $radix = 64;
713             }
714             elsif ($len >= 26) {
715             # if it didn't contain those characters and is this long
716 0         0 $radix = 32;
717             }
718             elsif ($len >= 22) {
719 0         0 $radix = 64;
720             }
721             else {
722             # the regex above should ensure this is never reached.
723 0         0 Carp::croak
724             ("Not sure what to do with an identifier of length $len.");
725             }
726             }
727              
728             # get this stuff back to canonical form
729 401         862 $version = _decode_version($version);
730             # warn $version;
731 401         1017 $content = $DECODE{$radix}->($content, $p->{align});
732             # warn unpack 'H*', $content;
733              
734             # reassemble the pair
735 401         1097 my $bin = $TRANSFORM[$p->{version}][1]->($version, $content);
736              
737             # *now* format.
738 401         830 $format->($bin);
739             }
740              
741             =head2 to_ncname_64 $UUID [, %PARAMS ]
742              
743             Shorthand for Base64 NCNames.
744              
745             =cut
746              
747             sub to_ncname_64 {
748 3     3 1 3538 to_ncname(@_, radix => 64);
749             }
750              
751             =head2 from_ncname_64 $NCNAME [, $FORMAT | %PARAMS ]
752              
753             Ditto.
754              
755             =cut
756              
757             sub from_ncname_64 {
758 200     200 1 109456 from_ncname(@_, radix => 64);
759             }
760              
761             =head2 to_ncname_32 $UUID [, %PARAMS ]
762              
763             Shorthand for Base32 NCNames.
764              
765             =cut
766              
767             sub to_ncname_32 {
768 2     2 1 14 to_ncname(shift, 32, @_);
769             }
770              
771             =head2 from_ncname_32 $NCNAME [, $FORMAT | %PARAMS ]
772              
773             Ditto.
774              
775             =cut
776              
777             sub from_ncname_32 {
778 200     200 1 214287 from_ncname(@_, radix => 32);
779             }
780              
781             =head1 AUTHOR
782              
783             Dorian Taylor, C<< >>
784              
785             =head1 BUGS
786              
787             Please report bugs/issues/etc L
788             GitHub|https://github.com/doriantaylor/p5-data-uuid-ncname/issues>.
789              
790             =over 4
791              
792             =item * MetaCPAN
793              
794             L
795              
796             =item * GitHub repository (bugs also go here)
797              
798             L
799              
800             =item * AnnoCPAN: Annotated CPAN documentation
801              
802             L
803              
804             =item * CPAN Ratings
805              
806             L
807              
808             =back
809              
810             =head1 SEE ALSO
811              
812             =over 4
813              
814             =item
815              
816             L
817              
818             =item
819              
820             L
821              
822             =item
823              
824             L
825              
826             =item
827              
828             L
829              
830             =item
831              
832             L
833              
834             =item
835              
836             L
837             (NCName)
838              
839             =item
840              
841             L
842             Datatypes|http://www.w3.org/TR/xmlschema11-2/#ID> (ID)
843              
844             =item
845              
846             L
847             (Revised)|http://www.w3.org/TR/rdf-syntax-grammar/#rdf-id>
848              
849             =item
850              
851             L
852              
853             =back
854              
855             This module lives under the C namespace for the purpose of
856             namespace hygiene. The main module I depend on
857             L, howevever the script L I depend on
858             L to generate UUIDs.
859              
860             =head1 LICENSE AND COPYRIGHT
861              
862             Copyright 2012-2018 Dorian Taylor.
863              
864             Licensed under the Apache License, Version 2.0 (the "License"); you
865             may not use this file except in compliance with the License. You may
866             obtain a copy of the License at
867             L .
868              
869             Unless required by applicable law or agreed to in writing, software
870             distributed under the License is distributed on an "AS IS" BASIS,
871             WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
872             implied. See the License for the specific language governing
873             permissions and limitations under the License.
874              
875             =cut
876              
877             1; # End of Data::UUID::NCName