File Coverage

blib/lib/Convert/BaseN.pm
Criterion Covered Total %
statement 37 42 88.1
branch 8 16 50.0
condition 3 7 42.8
subroutine 9 9 100.0
pod 3 3 100.0
total 60 77 77.9


.
line stmt bran cond sub pod time code
1             package Convert::BaseN;
2 4     4   245177 use warnings;
  4         11  
  4         141  
3 4     4   25 use strict;
  4         7  
  4         903  
4             our $VERSION = sprintf "%d.%02d", q$Revision: 0.1 $ =~ /(\d+)/g;
5 4     4   30 use Carp;
  4         20  
  4         14871  
6              
7             sub _make_tr($$;$) {
8 44     44   2791 my ( $from, $to, $opt ) = @_;
9 44   50     305 $opt ||= '';
10 44         22428 my $tr = eval qq{ sub{ \$_[0] =~ tr#$from#$to#$opt } };
11 44 50       134 croak $@ if $@;
12 44         111 $tr;
13             }
14              
15             my %h2q = qw{ 0 00 1 01 2 02 3 03 4 10 5 11 6 12 7 13
16             8 20 9 21 a 22 b 23 c 30 d 31 e 32 f 33 };
17             my %q2h = reverse %h2q;
18              
19             my %o2b = qw{ 0 000 1 001 2 010 3 011 4 100 5 101 6 110 7 111 };
20             my %b2o = reverse %o2b;
21              
22             my %v2b = do {
23             my $i = 0;
24             map { $_ => sprintf( "%05b", $i++ ) } ( '0' .. '9', 'A' .. 'V' );
25             };
26             my %b2v = reverse %v2b;
27              
28             my %gen_decoders = (
29             2 => sub {
30             my ( $chars ) = @_;
31             my $tr = $chars ? _make_tr( $chars, '01' ) : undef;
32             sub {
33             my $str = shift;
34             $tr->($str) if $tr;
35             $str =~ tr/01//cd;
36             scalar pack "B*", $str;
37             }
38             },
39             4 => sub {
40             my ($chars) = @_;
41             my $tr = $chars ? _make_tr( $chars, '0123' ) : undef;
42             sub {
43             my $str = shift;
44             $tr->($str) if $tr;
45             $str =~ tr/0123//cd;
46             $str =~ s/(..)/$q2h{$1}/g;
47             scalar pack "H*", $str;
48             }
49             },
50             8 => sub {
51             my ($chars) = @_;
52             my $tr = $chars ? _make_tr( $chars, '0-7=' ) : undef;
53             sub {
54             my $str = shift;
55             $tr->($str) if $tr;
56             $str =~ tr/0-7//cd;
57             $str =~ s/(.)/$o2b{$1}/g;
58             my $padlen = (length $str) % 8;
59             $str =~ s/0{$padlen}\z//;
60             scalar pack "B*", $str;
61             }
62             },
63             16 => sub {
64             my ($chars) = @_;
65             my $tr = $chars ? _make_tr( $chars, '0-9a-f' ) : undef;
66             sub {
67             my $str = shift;
68             $tr->($str) if $tr;
69             $str =~ tr/0-9a-f//cd;
70             scalar pack "H*", lc $str;
71             }
72             },
73             32 => sub {
74             my ($chars) = @_;
75             my $tr = $chars ? _make_tr( $chars, '0-9A-V=' ) : undef;
76             sub {
77             my $str = shift;
78             $tr->($str) if $tr;
79             $str =~ tr/0-9A-V//cd;
80             $str =~ s/(.)/$v2b{$1}/g;
81             my $padlen = (length $str) % 8;
82             $str =~ s/0{$padlen}\z//;
83             scalar pack "B*", $str;
84             }
85             },
86             64 => sub {
87             require MIME::Base64;
88             my ($chars) = @_;
89             my $tr = $chars ? _make_tr( $chars, '0-9A-Za-z+/=' ) : undef;
90             sub {
91             my $str = shift;
92             $tr->($str) if $tr;
93             MIME::Base64::decode($str);
94             }
95             }
96             );
97              
98             sub _fold_line {
99 40     40   91 my ( $str, $lf, $cpl ) = @_;
100 40 100       110 $lf = "\n" unless defined $lf;
101             # warn ord $lf;
102 40 100       451 return $str unless $lf;
103 8   50     186 $cpl ||= 76;
104 8         429 $str =~ s/(.{$cpl})/$1$lf/gms;
105 8         348 $str;
106             }
107              
108             my %gen_encoders = (
109             2 => sub {
110             my ($chars) = @_;
111             my $tr = $chars ? _make_tr( '01', $chars ) : undef;
112             sub ($;$$) {
113             my ( $str, $lf, $cpl ) = @_;
114             my $ret = unpack "B*", $str;
115             $tr->($ret) if $tr;
116             _fold_line( $ret, $lf, $cpl );
117             }
118             },
119             4 => sub {
120             my ($chars) = @_;
121             my $tr = $chars ? _make_tr( '0123', $chars ) : undef;
122             sub ($;$) {
123             my ( $str, $lf, $cpl ) = @_;
124             my $ret = unpack "H*", $str;
125             $ret =~ s/(.)/$h2q{$1}/g;
126             $tr->($ret) if $tr;
127             _fold_line( $ret, $lf, $cpl );
128             }
129             },
130             8 => sub {
131             my ( $chars, $nopad ) = @_;
132             my $tr = $chars ? _make_tr( '0-7=', $chars ) : undef;
133             sub ($;$$) {
134             my ( $str, $lf, $cpl ) = @_;
135             my $ret = unpack "B*", $str;
136             $ret .= 0 while ( length $ret ) % 3;
137             $ret =~ s/(...)/$b2o{$1}/g;
138             $nopad or do{ $ret .= '=' while ( length $ret ) % 8 };
139             $tr->($ret) if $tr;
140             _fold_line( $ret, $lf, $cpl );
141             }
142             },
143             16 => sub {
144             my ($chars) = @_;
145             my $tr = $chars ? _make_tr( '0-9a-f', $chars ) : undef;
146             sub ($;$$) {
147             my ( $str, $lf, $cpl ) = @_;
148             my $ret = unpack "H*", $str;
149             $tr->($ret) if $tr;
150             _fold_line( $ret, $lf, $cpl );
151             }
152             },
153             32 => sub {
154             my ( $chars, $nopad ) = @_;
155             my $tr = $chars ? _make_tr( '0-9A-V=', $chars ) : undef;
156             sub ($;$$) {
157             my ( $str, $lf, $cpl ) = @_;
158             my $ret = unpack "B*", $str;
159             $ret .= 0 while ( length $ret ) % 5;
160             $ret =~ s/(.....)/$b2v{$1}/g;
161             $nopad or do{ $ret .= '=' while ( length $ret ) % 8 };
162             $tr->($ret) if $tr;
163             _fold_line( $ret, $lf, $cpl );
164             }
165             },
166             64 => sub {
167             require MIME::Base64;
168             my ( $chars, $nopad ) = @_;
169             my $tr = $chars ? _make_tr( '0-9A-Za-z+/=', $chars ) : undef;
170             sub ($;$$) {
171             my ( $str, $lf, $cpl ) = @_;
172             $str =
173             defined $lf
174             ? _fold_line( MIME::Base64::encode( $str, '' ), $lf, $cpl )
175             : MIME::Base64::encode( $str, $lf );
176             $str =~ tr/=//d if $nopad;
177             $tr->($str) if $tr;
178             $str;
179             }
180             }
181             );
182              
183             sub _base64_decode_any {
184 27     27   171 require MIME::Base64;
185 27         53 my $str = shift;
186 27         50 $str =~ tr{\-\_\+\,\[\]}{+/+/+/};
187 27         91 local $^W = 0; # in case the string is not padded
188 27         209 MIME::Base64::decode($str);
189             }
190              
191              
192             our %named_decoder = (
193             base2 => $gen_decoders{2}->(),
194             base4 => $gen_decoders{4}->(),
195             DNA => $gen_decoders{4}->('ACGT'),
196             RNA => $gen_decoders{4}->('UGCA'),
197             base8 => $gen_decoders{8}->(),
198             base16 => $gen_decoders{16}->('0-9A-F'),
199             base32 => $gen_decoders{32}->('A-Z2-7='),
200             base32hex => $gen_decoders{32}->(),
201             base64 => \&_base64_decode_any,
202             base64_url => \&_base64_decode_any,
203             base64_imap => \&_base64_decode_any,
204             base64_ircu => \&_base64_decode_any,
205             );
206              
207             our %named_encoder = (
208             base2 => $gen_encoders{2}->(),
209             base4 => $gen_encoders{4}->(),
210             DNA => $gen_encoders{4}->('ACGT'),
211             RNA => $gen_encoders{4}->('UGCA'),
212             base8 => $gen_encoders{8}->(),
213             base16 => $gen_encoders{16}->('0-9A-F'),
214             base32 => $gen_encoders{32}->('A-Z2-7='),
215             base32hex => $gen_encoders{32}->(),
216             base64 => $gen_encoders{64}->(),
217             base64_url => $gen_encoders{64}->( '0-9A-Za-z\-\_=', 1 ),
218             base64_imap => $gen_encoders{64}->('0-9A-Za-z\+\,='),
219             base64_ircu => $gen_encoders{64}->('0-9A-Za-z\[\]='),
220             );
221              
222             sub new {
223 36     36 1 25240 my $pkg = shift;
224 36 50       272 my %opt = @_ == 1 ? ( name => shift ) : @_;
225 36         48 my ( $encoder, $decoder );
226 36 50       96 if ( $opt{name} ) {
227 36         88 $decoder = $named_decoder{ $opt{name} };
228 36         75 $encoder = $named_encoder{ $opt{name} };
229 36 50 33     230 croak "$opt{name} unknown" unless $decoder and $encoder;
230             }
231             else {
232 0         0 eval {
233 0 0       0 my $nopad = exists $opt{padding} ? !$opt{padding}
234             : $opt{nopadding};
235 0         0 $decoder = $gen_decoders{ $opt{base} }->( $opt{chars} );
236 0         0 $encoder = $gen_encoders{ $opt{base} }->( $opt{chars}, $nopad );
237             };
238 0 0       0 croak "base $opt{base} unknown" if $@;
239             }
240 36         241 bless {
241             decoder => $decoder,
242             encoder => $encoder,
243             }, $pkg;
244             }
245              
246 56     56 1 395 sub decode { my $self = shift; $self->{decoder}->(@_) }
  56         169  
247 44     44 1 29456 sub encode { my $self = shift; $self->{encoder}->(@_) }
  44         182  
248              
249             if (__FILE__ eq $0){
250             my ($bn, $encoded);
251              
252             $bn = __PACKAGE__->new(base => 2, chars => '<>');
253             $encoded = $bn->encode("dankogai", " ");
254             warn $encoded;
255             warn $bn->decode($encoded);
256              
257             $bn = __PACKAGE__->new(base => 4, chars => 'ACGT');
258             $encoded = $bn->encode("dankogai", " ");
259             warn $encoded;
260             warn $bn->decode($encoded);
261             $bn = __PACKAGE__->new(base => 8, chars => 'abcdefgh=');
262             $encoded = $bn->encode("dankogai");
263             warn $encoded;
264             warn $bn->decode($encoded);
265             warn length $bn->decode($encoded);
266              
267             $bn = __PACKAGE__->new(base => 16, chars => '0-9A-F');
268             $encoded = $bn->encode("dankogai", " ");
269             warn $encoded;
270              
271             $bn = __PACKAGE__->new(base => 32);
272             $encoded = $bn->encode("dankogai");
273             warn $encoded;
274             warn $bn->decode($encoded);
275             warn length $bn->decode($encoded);
276              
277             $bn = __PACKAGE__->new(base => 32, chars => 'A-Z2-7=');
278             $encoded = $bn->encode("dankogai");
279             warn $encoded;
280             warn $bn->decode($encoded);
281             warn length $bn->decode($encoded);
282              
283             $bn = __PACKAGE__->new(base => 64);
284             $encoded = $bn->encode("dankogai");
285             warn $encoded;
286             warn $bn->decode($encoded);
287              
288             $bn = __PACKAGE__->new(base => 64,chars => '0-9A-Za-z\-_=');
289             $encoded = $bn->encode(join("", map {chr} 0x21 .. 0x7e), "\n", 40);
290             warn $encoded;
291             warn $bn->decode($encoded);
292             warn scalar unpack "H*", $bn->decode('-__-');
293              
294             $bn = __PACKAGE__->new('base69');
295             #warn $bn->encode("dankogai");
296             #$bn = __PACKAGE__->new(name => 'base4');
297             #$bn = __PACKAGE__->new(name => 'basex');
298             #$bn = __PACKAGE__->new(base => 17);
299             }
300              
301             1; # End of Convert::BaseN
302              
303             =head1 NAME
304              
305             Convert::BaseN - encoding and decoding of base{2,4,8,16,32,64} strings
306              
307             =head1 VERSION
308              
309             $Id: BaseN.pm,v 0.1 2008/06/16 17:34:27 dankogai Exp dankogai $
310              
311             =cut
312              
313             =head1 SYNOPSIS
314              
315             use Convert::BaseN;
316             # by name
317             my $cb = Convert::BaseN->new('base64');
318             my $cb = Convert::BaseN->new( name => 'base64' );
319             # or base
320             my $cb = Convert::BaseN->new( base => 64 );
321             my $cb_url = Convert::BaseN->new(
322             base => 64,
323             chars => '0-9A-Za-z\-_='
324             );
325             # encode and decode
326             $encoded = $cb->encode($data);
327             $decoded = $cb->decode($encoded);
328              
329             =head1 EXPORT
330              
331             Nothing. Instead of that, this module builds I for
332             you and you use its C and C methods to get the job
333             done.
334              
335             =head1 FUNCTIONS
336              
337             =head2 new
338              
339             Create the transcoder object.
340              
341             # by name
342             my $cb = Convert::BaseN->new('base64');
343             my $cb = Convert::BaseN->new( name => 'base64' );
344             # or base
345             my $cb = Convert::BaseN->new( base => 64 );
346             my $cb_url = Convert::BaseN->new(
347             base => 64,
348             chars => '0-9A-Za-z\-_='
349             );
350              
351             You can pick the decoder by name or create your own by specifying base
352             and character map.
353              
354             =over 2
355              
356             =item base
357              
358             Must be 2, 4, 16, 32 or 64.
359              
360             =item chars
361              
362             Specifiles the character map. The format is the same as C
363              
364             # DNA is coded that way.
365             my $dna = Convert::BaseN->new( base => 4, chars => 'ACGT' );
366              
367             =item padding
368              
369             =item nopadding
370              
371             Specifies if padding (adding '=' or other chars) is required when
372             encoding. default is yes.
373              
374             # url-safe Base64
375             my $b64url = Convert::BaseN->new(
376             base => 64, chars => '0-9A-Za-z\-_=', padding => 0;
377             );
378              
379             =item name
380              
381             When specified, the following pre-defined encodings will be used.
382              
383             =over 2
384              
385             =item base2
386              
387             base 2 encoding. C is C<01110000011001010111001001101100>.
388              
389             =item base4
390              
391             =item DNA
392              
393             =item RNA
394              
395             base 4 encodings. C is:
396              
397             base4: 1300121113021230
398             DNA: CTAACGCCCTAGCGTA
399             RNA: GAUUGCGGGAUCGCAU
400              
401             base 16 encoding. C is C<7065726c>.
402              
403             =item base32
404              
405             =item base32hex
406              
407             base 32 encoding mentioned in RFC4648. C is:
408              
409             base32: OBSXE3A==
410             base32hex: E1IN4R0==
411              
412             =item base64
413              
414             =item base64_url
415              
416             =item base64_imap
417              
418             =item base64_ircu
419              
420             base 64 encoding, as in L. They differ only in
421             characters to represent number 62 and 63 as follows.
422              
423             base64: +/
424             base64_url: -_
425             base64_imap: +,
426             base64_ircu: []
427              
428             for all predefined base 64 variants, C accept ANY form of those.
429              
430             =back
431              
432             =back
433              
434             =head2 decode
435              
436             Does decode
437              
438             my $decoded = $cb->decode($data)
439              
440             =head2 encode
441              
442             Does encode.
443              
444             # line folds every 76 octets, like MIME::Base64::encode
445             my $encoded = $cb->encode($data);
446             # no line folding (compatibile w/ MIME::Base64)
447             my $encoded = $cb->encode($data, "");
448             # line folding by CRLF, every 40 octets
449             my $encoded = $cb->encode($data, "\r\n", 40);
450              
451             =head1 SEE ALSO
452              
453             RFC4648 L
454              
455             Wikipedia L
456              
457             L
458              
459             L
460              
461             L
462              
463             L
464              
465             =head1 AUTHOR
466              
467             Dan Kogai, C<< >>
468              
469             =head1 BUGS
470              
471             Please report any bugs or feature requests to C, or through
472             the web interface at L. I will be notified, and then you'll
473             automatically be notified of progress on your bug as I make changes.
474              
475             =head1 SUPPORT
476              
477             You can find documentation for this module with the perldoc command.
478              
479             perldoc Convert::BaseN
480              
481             You can also look for information at:
482              
483             =over 4
484              
485             =item * RT: CPAN's request tracker
486              
487             L
488              
489             =item * AnnoCPAN: Annotated CPAN documentation
490              
491             L
492              
493             =item * CPAN Ratings
494              
495             L
496              
497             =item * Search CPAN
498              
499             L
500              
501             =back
502              
503             =head1 ACKNOWLEDGEMENTS
504              
505             N/A
506              
507             =head1 COPYRIGHT & LICENSE
508              
509             Copyright 2008 Dan Kogai, all rights reserved.
510              
511             This program is free software; you can redistribute it and/or modify it
512             under the same terms as Perl itself.
513              
514              
515             =cut
516              
517