File Coverage

blib/lib/String/CodiceFiscale.pm
Criterion Covered Total %
statement 354 392 90.3
branch 144 178 80.9
condition 51 70 72.8
subroutine 40 41 97.5
pod 16 26 61.5
total 605 707 85.5


line stmt bran cond sub pod time code
1             package String::CodiceFiscale;
2              
3             $String::CodiceFiscale::VERSION = '0.02';
4              
5 1     1   731 use strict;
  1         2  
  1         23  
6 1     1   4 use utf8;
  1         2  
  1         5  
7 1     1   19 no locale;
  1         1  
  1         12  
8 1     1   30 use base qw(Class::Data::Inheritable);
  1         1  
  1         426  
9 1     1   730 use Time::Piece;
  1         8553  
  1         5  
10 1     1   60 use Carp;
  1         3  
  1         56  
11 1     1   443 use POSIX;
  1         5248  
  1         4  
12              
13             our %CRC = (
14             A => [0, 1], B => [1, 0], C => [2, 5],
15             D => [3, 7], E => [4, 9], F => [5, 13],
16             G => [6, 15], H => [7, 17], I => [8, 19],
17             J => [9, 21], K => [10, 2], L => [11, 4],
18             M => [12, 18], N => [13, 20], O => [14, 11],
19             P => [15, 3], Q => [16, 6], R => [17, 8],
20             S => [18, 12], T => [19, 14], U => [20, 16],
21             V => [21, 10], W => [22, 22], X => [23, 25],
22             Y => [24, 24], Z => [25, 23], 0 => [0, 1],
23             1 => [1, 0], 2 => [2, 5], 3 => [3, 7],
24             4 => [4, 9], 5 => [5, 13], 6 => [6, 15],
25             7 => [7, 17], 8 => [8, 19], 9 => [9, 21],
26             );
27              
28             __PACKAGE__->mk_classdata('ERROR');
29              
30             our ($MONTHS, @MONTHS, %MONTHS); #code to/from month
31             @MONTHS[1..12] = qw(A B C D E H L M P R S T);
32             @MONTHS{@MONTHS[1..12]} = 1..12;
33             $MONTHS = join '', @MONTHS[1..12];
34              
35             our ($XNUMS, @XNUMS, %XNUMS); #coded numbers for rare collision cases
36             @XNUMS = qw(L M N P Q R S T U V);
37             @XNUMS{@XNUMS} = 0..9; #not used anymore, but here "just in case"
38             $XNUMS = join '', @XNUMS;
39              
40             our $CONSONANTS = 'BCDFGHJKLMNPQRSTVWXYZ';
41             our $VOWELS = 'AEIOU';
42              
43             our $RE_cf = qr/
44             ^ #start
45             ([A-Z]{3}) #surname coded
46             ([A-Z]{3}) #firstname coded
47             ([\d$XNUMS]{2}) #year
48             ([$MONTHS]) #month coded
49             ([\d$XNUMS]{2}) #day and sex
50             ([A-Z][\d$XNUMS]{3}) #birthplace coded
51             ([A-Z]) #crc
52             $ #end
53             /xo;
54              
55             our $RE_nc = qr/^[$CONSONANTS]*[$VOWELS]*X*$/xo;
56              
57             our %OPTS = map {$_ => 1} qw(
58             sn sn_c fn fn_c date year year_c
59             month month_c day day_c sex bp bp_c
60             );
61              
62             sub new {
63 12     12 1 466 my $class = shift;
64 12         23 my $self = bless {}, $class;
65 12         37 while (my ($k, $v) = splice(@_, 0, 2)) {
66 57 50       99 $self->_croak(qq(Not such an options "$k")) unless $OPTS{$k};
67 57         111 $self->$k($v);
68             }
69 12         36 return $self;
70             }
71              
72             sub sn {
73 9     9 1 244 my $self = shift;
74 9         17 my ($sn) = @_;
75 9 100       20 if (defined $sn) {
76 7         14 $sn = uc($sn);
77 7         13 $self->{sn} = $sn;
78 7         12 $self->{sn_c} = undef;
79 7         9 $self->{sn_re} = undef;
80             }
81 9         19 return $sn;
82             }
83              
84             sub sn_c {
85 38     38 0 54 my $self = shift;
86 38         58 my ($sn_c) = @_;
87 38 100       68 if (defined $sn_c) {
88 17         26 $sn_c = uc($sn_c);
89 17 100       93 unless ($sn_c =~ /$RE_nc/) {
90 1         4 $self->error('Coded surname cannot contain ' .
91             'vowels followed by consonants');
92 1         4 return;
93             }
94 16 100       32 unless (length($sn_c) == 3) {
95 2         6 $self->error('Coded surname must be 3 chars in length');
96 2         7 return;
97             }
98 14         24 $self->{sn_c} = $sn_c;
99 14         19 $self->{sn} = undef;
100 14         24 $self->{sn_re} = undef;
101             }
102 35 100 100     85 if (defined $self->{sn} and not defined $self->{sn_c}) {
103 5         6 my $temp = '';
104             OUTER: {
105 5         7 while ($self->{sn} =~ /([$CONSONANTS])/go) {
  5         34  
106 11         20 $temp .= $1;
107 11 100       35 last OUTER if length $temp >= 3;
108             }
109 3         18 while ($self->{sn} =~ /([$VOWELS])/go) {
110 3         7 $temp .= $1;
111 3 100       9 last OUTER if length $temp >= 3;
112             }
113 1         4 while (length $temp < 3) {
114 1         2 $temp .= 'X';
115             }
116             }
117 5         9 $self->{sn_c} = $temp;
118             }
119 35         79 return $self->{sn_c};
120             }
121              
122             sub sn_re {
123 3     3 0 4 my $self = shift;
124 3         9 return $self->_n_re('sn_c');
125             }
126              
127              
128             sub sn_match {
129 2     2 1 6 my $self = shift;
130 2         5 my ($tm) = @_;
131 2 50       5 return unless defined $tm;
132 2         4 $tm = uc $tm;
133 2         6 $self->_fix_name($tm);
134 2 50       4 if (defined(my $sn = $self->sn)) {
135 0         0 $self->_fix_name($sn);
136 0         0 return $tm eq $self->sn;
137             }
138 2 50       3 if (defined $self->sn_c) {
139 2         5 return $tm =~ $self->sn_re;
140             }
141 0         0 return;
142             }
143              
144             sub fn {
145 13     13 1 16 my $self = shift;
146 13         22 my ($fn) = @_;
147 13 100       24 if (defined $fn) {
148 9         16 $fn = uc($fn);
149 9         16 $self->{fn} = $fn;
150 9         10 $self->{fn_c} = undef;
151 9         13 $self->{fn_re} = undef;
152             }
153 13         27 return $fn;
154             }
155              
156             sub fn_c {
157 46     46 0 58 my $self = shift;
158 46         93 my ($fn_c) = @_;
159 46 100       72 if (defined $fn_c) {
160 19         26 $fn_c = uc($fn_c);
161 19 100       95 unless ($fn_c =~ /$RE_nc/) {
162 1         4 $self->error('Coded name cannot contain ' .
163             'vowels followed by consonants');
164 1         4 return;
165             }
166 18 100       37 unless (length($fn_c) == 3) {
167 2         7 $self->error('Coded name must be 3 chars in length');
168 2         6 return;
169             }
170 16         34 $self->{fn_c} = $fn_c;
171 16         22 $self->{fn} = undef;
172 16         26 $self->{fn_re} = undef;
173             }
174 43 100 100     96 if (defined $self->{fn} and not defined $self->{fn_c}) {
175 7         10 my $temp = '';
176 7         15 my $skip = $self->_count_consonants($self->{fn}) > 3;
177             OUTER: {
178 7         9 while ($self->{fn} =~ /([$CONSONANTS])/go) {
  7         33  
179 19 100 100     45 if ($skip and length($temp) == 1) {
180 3         5 $skip = 0;
181 3         7 next;
182             }
183 16         21 $temp .= $1;
184 16 100       39 last OUTER if length $temp >= 3;
185             }
186 3         15 while ($self->{fn} =~ /([$VOWELS])/go) {
187 4         5 $temp .= $1;
188 4 100       12 last OUTER if length $temp >= 3;
189             }
190 1         3 while (length $temp < 3) {
191 1         3 $temp .= 'X';
192             }
193             }
194 7         10 $self->{fn_c} = $temp;
195             }
196 43         102 return $self->{fn_c};
197             }
198              
199             sub fn_re {
200 5     5 0 8 my $self = shift;
201 5         11 return $self->_n_re('fn_c');
202             }
203              
204             sub fn_match {
205 4     4 1 6 my $self = shift;
206 4         8 my ($tm) = @_;
207 4 50       9 return unless defined $tm;
208 4         5 $tm = uc $tm;
209 4         12 $self->_fix_name($tm);
210 4 50       9 if (defined(my $fn = $self->fn)) {
211 0         0 $self->_fix_name($fn);
212 0         0 return $tm eq $fn;
213             }
214 4 50       7 if (defined $self->fn_c) {
215 4         8 return $tm =~ $self->fn_re;
216             }
217 0         0 return;
218             }
219              
220              
221             sub date {
222 7     7 1 256 my $self = shift;
223 7         11 my ($date) = @_;
224 7 100       13 if (defined $date) {
225 6         7 my $t;
226 6         8 eval { $t = Time::Piece->strptime($date, '%Y-%m-%d') };
  6         18  
227 6 100       303 if ($@) {
228 1         4 $self->error("Invalid date");
229 1         3 return;
230             }
231 5         13 my %date = (
232             year => $t->year,
233             month => $t->mon,
234             day => $t->mday,
235             );
236 5         91 for (qw(year month day)) {
237 15 50       33 unless ( $self->$_($date{$_}) ) {
238 0         0 $self->error("Couldn't parse $_");
239 0         0 return;
240             }
241             }
242             } else {
243 1         2 my %date;
244 1         4 for (qw(year month day)) {
245 3         7 $date{$_} = $self->$_;
246 3 50       9 unless (defined $date{$_}) {
247 0         0 $self->error("Couldn't retrieve $_");
248 0         0 return;
249             }
250             }
251 1         9 return sprintf("%04d-%02d-%02d", @date{qw(year month day)});
252             }
253 5         19 return $date;
254             }
255              
256             sub year {
257 9     9 1 13 my $self = shift;
258 9         14 my ($y) = @_;
259 9 100       19 if (defined $y) {
260 7 100       35 unless ($y =~ /^\d+$/) {
261 1         4 $self->error('A year should be an unsigned integer');
262 1         4 return;
263             }
264 6         31 $self->{year} = $y;
265 6         12 $self->{year_c} = undef;
266             }
267 8 100 66     35 if (not defined $self->{year} and defined $self->{year_c}) {
268 2         5 my $year = $self->_xnums($self->{year_c});
269 2         11 my $this_year = (localtime(time))[5] + 1900;
270 2         155 my $twodigits_year = $this_year % 100; # this is making a guess
271 2         11 my $century = floor($this_year/100);
272 2 50       14 $self->{year} = sprintf "%d%02d",
273             $century - ($year > $twodigits_year ? 1 : 0),
274             $year;
275             }
276 8         30 return $self->{year};
277             }
278              
279             sub year_c {
280 22     22 0 30 my $self = shift;
281 22         30 my ($ycx) = @_;
282 22 100       35 if (defined $ycx) {
283 9         21 my $yc = $self->_xnums($ycx);
284 9 100       23 unless ($yc =~ /^\d\d$/) {
285 1         4 $self->error('A year in Codice Fiscale is 2 digit long');
286 1         4 return;
287             }
288 8         15 $self->{year_c} = $ycx;
289 8         17 $self->{year} = undef;
290             }
291 21 100 66     52 if (not defined $self->{year_c} and defined $self->{year}) {
292 1         5 $self->{year_c} = sprintf("%02d", $self->{year} % 100);
293             }
294 21         44 return $self->{year_c};
295             }
296              
297             sub month {
298 11     11 1 19 my $self = shift;
299 11         15 my ($m) = @_;
300 11 100       21 if (defined $m) {
301 8 100 66     46 unless ($m =~ /^\d+$/ and $m >= 1 and $m <= 12) {
      100        
302 2         6 $self->error('Month must be numeric and between 1 and 12');
303 2         6 return;
304             }
305 6         12 $self->{month} = $m;
306 6         9 $self->{month_c} = undef;
307             }
308 9 100 66     24 if (not defined $self->{month} and defined $self->{month_c}) {
309 2         6 $self->{month} = $MONTHS{$self->{month_c}};
310             }
311 9         23 return $self->{month};
312             }
313              
314             sub month_c {
315 22     22 0 28 my $self = shift;
316 22         33 my ($mc) = @_;
317 22 100       40 if (defined $mc) {
318 8 50       33 unless ($mc =~ /^[$MONTHS]$/o) {
319 0         0 $self->error('Month not correctly encoded');
320 0         0 return;
321             }
322 8         12 $self->{month_c} = $mc;
323 8         14 $self->{month} = undef;
324             }
325 22 100 66     43 if (not defined $self->{month_c} and defined $self->{month}) {
326 2         5 $self->{month_c} = $MONTHS[$self->{month}];
327             }
328 22         41 return $self->{month_c};
329             }
330              
331             sub day {
332 10     10 1 12 my $self = shift;
333 10         16 my ($d) = @_;
334 10 100       21 if (defined $d) {
335 7 100 33     38 unless ($d =~ /^\d+$/ and 1 <= $d and $d <= 31) {
      66        
336 1         3 $self->error('Day is out of range');
337 1         3 return;
338             }
339 6         11 $self->{day} = $d;
340 6         9 $self->{day_c} = undef;
341             }
342 9 100 66     24 if (not defined $self->{day} and defined $self->{day_c}) {
343 2         5 my $dayx = $self->_xnums($self->{day_c});
344 2 100       7 $self->{day} = $dayx > 40 ? $dayx - 40 : $dayx;
345             }
346 9         32 return $self->{day};
347             }
348              
349             sub day_c {
350 24     24 0 29 my $self = shift;
351 24         32 my ($dcx) = @_;
352 24 100       53 if (defined $dcx) {
353 9         18 my $dc = $self->_xnums($dcx);
354 9 50       26 unless ($dc =~ /^\d+$/) {
355 0         0 $self->error('Invalid coding of day');
356 0         0 return;
357             }
358 9 100 100     56 unless ($dc > 0 and not ($dc > 31 and $dc < 41) and $dc <= 71) {
      66        
      66        
359 1         4 $self->error('Day out of range');
360 1         4 return;
361             }
362 8         16 $self->{day_c} = $dcx;
363 8         12 $self->{day} = undef;
364 8         12 $self->{sex} = undef;
365             }
366 23 50 66     52 if (not defined $self->{day_c} and defined $self->{day}
      66        
367             and defined $self->{sex}) {
368 3         7 $self->{day_c} = $self->{day};
369 3 100       8 $self->{day_c} += 40 if $self->{sex} eq 'F';
370 3         9 $self->{day_c} = sprintf("%02d", $self->{day_c});
371             }
372 23         52 return $self->{day_c};
373             }
374              
375             sub sex {
376 7     7 1 12 my $self = shift;
377 7         11 my ($sex) = @_;
378 7 100       14 if (defined $sex) {
379 5 50       14 unless ($sex =~ /^[MF]$/i) {
380 0         0 $self->error('Sex can be either "M" or "F"');
381 0         0 return;
382             }
383 5         11 $self->{sex} = $sex;
384 5         6 $self->{day_c} = undef;
385             }
386 7 100 66     19 if (not defined $self->{sex} and defined $self->{day_c}) {
387 2         7 my $dayx = $self->_xnums($self->{day_c});
388 2 100       7 $self->{sex} = $dayx > 40 ? 'F' : 'M';
389             }
390 7         20 return $self->{sex};
391             }
392              
393             sub bp {
394 9     9 1 14 my $self = shift;
395 9         13 my ($bp) = @_;
396 9 100       18 if (defined $bp) {
397 6 100       16 unless ($bp =~ /^[A-Z]\d\d\d$/) { # to improve further?
398 2         5 $self->error('Invalid birthplace code');
399 2         8 return;
400             }
401 4         5 $self->{bp} = $bp;
402 4         9 $self->{bp_c} = undef;
403             }
404 7 100 66     18 if (not defined $self->{bp} and defined $self->{bp_c}) {
405 2         3 my $bpc = $self->{bp_c};
406 2         7 substr($bpc, 1) = $self->_xnums(substr($bpc, 1));
407 2         5 $self->{bp} = $bpc;
408             }
409 7         23 return $self->{bp};
410             }
411              
412             sub bp_c {
413 23     23 0 28 my $self = shift;
414 23         32 my ($bpcx) = @_;
415 23 100       38 if (defined $bpcx) {
416 10         13 my $bpc = $bpcx;
417 10         23 substr($bpc, 1) = $self->_xnums(substr($bpc, 1));
418 10 100       29 unless ($bpc =~ /^[A-Z]\d\d\d$/) { # to improve further?
419 1         4 $self->error('Invalid birthplace code');
420 1         4 return;
421             }
422 9         16 $self->{bp_c} = $bpcx;
423 9         15 $self->{bp} = undef;
424             }
425 22 100 66     40 if (not defined $self->{bp_c} and defined $self->{bp}) {
426 1         2 $self->{bp_c} = $self->{bp};
427             }
428 22         44 return $self->{bp_c};
429             }
430              
431             sub bd_c {
432 13     13 0 16 my $self = shift;
433 13         17 my $bdc = '';
434 13         18 for (qw(year_c month_c day_c)) {
435 39         66 my $t = $self->$_;
436 39 50       73 unless (defined $t) {
437 0         0 $self->error("Could not produce $_: some data is missing");
438 0         0 return;
439             }
440 39         55 $bdc .= $t;
441             }
442 13         21 return $bdc;
443             }
444              
445             sub cf {
446 5     5 1 13 my ($self, $dupe) = (@_);
447 5         12 return $self->_crc(1, $dupe);
448             }
449              
450             sub crc {
451 8     8 1 266 my $self = shift;
452 8         16 return $self->_crc(0);
453             }
454              
455             sub cf_nocrc {
456 13     13 0 16 my $self = shift;
457 13         15 my $cf = '';
458 13         23 for (qw(sn_c fn_c bd_c bp_c)) {
459 52         90 my $t = $self->$_;
460 52 50       85 unless (defined $t) {
461 0         0 $self->error("Could not produce $_: some data is missing");
462 0         0 return;
463             }
464 52         105 $cf .= $t;
465             }
466            
467 13         20 return $cf;
468             }
469              
470             sub _crc {
471 13     13   16 my $self = shift;
472 13         20 my ($cf_out, $dupe) = @_;
473 13         21 my $cf = $self->cf_nocrc;
474 13 50       24 unless ($cf) {
475 0         0 $self->error("Cannot produce a Codice Fiscale: missing data");
476 0         0 return;
477             }
478 13 100       20 if ($dupe) {
479 3         6 $dupe %= 128;
480 3         7 my @bitmap = split('', sprintf("%07b", _bmaker($dupe)));
481 3         8 my ($tcf, $i) = ($cf, 0);
482 3         17 while ($cf =~ /(\d)/g) {
483 21 100       42 substr($tcf, pos($cf) - 1, 1, $XNUMS[$1]) if $bitmap[$i];
484 21         38 $i++
485             }
486 3         7 $cf = $tcf;
487             }
488 13         15 my $count = 0;
489 13         25 for (my $i = 0; $i <= 14; $i++) {
490 195         350 $count += $CRC{substr($cf, $i, 1)}[($i + 1) % 2];
491             }
492 13         14 $count %= 26;
493 13 100       67 return ($cf_out ? $cf : '') . chr(65 + $count);
494             }
495              
496              
497             sub parse {
498 7     7 1 513 my $proto = shift;
499 7         13 my ($cf) = @_;
500 7         12 $cf = uc $cf;
501 7 50       16 unless (length($cf) == 16) {
502 0         0 $proto->error('A valid Codice Fiscale must be exactly 16 chars long');
503 0         0 return;
504             }
505 7         58 my ($sn, $fn, $year, $month, $dayx, $born, $crc) = $cf =~ /$RE_cf/;
506 7 50       16 unless ($crc) {
507 0         0 $proto->error('Cannot parse: invalid format');
508 0         0 return;
509             }
510              
511 7         17 my $obj = $proto->new(
512             sn_c => $sn,
513             fn_c => $fn,
514             year_c => $year,
515             month_c => $month,
516             day_c => $dayx,
517             bp_c => $born,
518             );
519              
520 7 100       15 unless ($crc eq $obj->crc) {
521 1         5 $proto->error('Invalid control character');
522 1         9 return;
523             }
524 6         20 return $obj;
525             }
526              
527             sub validate {
528 2     2 1 4 my $proto = shift;
529 2         4 my ($cf) = @_;
530 2         5 my $obj = $proto->parse($cf);
531 2 50       13 return 1 if $obj;
532 0         0 return;
533             }
534              
535              
536             sub error {
537 17     17 1 22 my $proto = shift;
538 17         24 my ($err) = @_;
539 17 100       33 if (ref $proto) {
540 16 50       34 $proto->{_err} = $err if defined $err;
541 16         29 return $proto->{_err};
542             }
543            
544 1 50       7 $proto->ERROR($err) if defined $err;
545 1         11 return $proto->ERROR;
546             }
547              
548             {
549              
550             my $tr_xnums = eval "sub {\$_[0] =~ tr/$XNUMS/0123456789/}";
551              
552             sub _xnums {
553 36     36   45 my $self = shift;
554 36         51 my ($nums) = @_;
555 36 50       162 return unless $nums =~ /^[\d$XNUMS]+$/o;
556 36         718 $tr_xnums->($nums);
557 36         72 return $nums;
558             }
559              
560             }
561              
562             sub _n_re {
563 8     8   11 my $self = shift;
564 8         11 my ($method) = @_;
565 8         36 (my $attr = $method) =~ s/_c$/_re/;
566 8 100       48 return $self->{$attr} if defined $self->{$attr};
567 6         14 my $nc = $self->$method;
568 6 50       13 unless ($nc) {
569 0 0       0 $self->error('There is no coded ' .
570             ($method eq 'sn_c' ? 'sur' : '') . 'name set');
571 0         0 return;
572             }
573            
574 6         88 my ($c, $v, $x) = $nc =~ /^([$CONSONANTS]*)([$VOWELS]*)(X*)$/o;
575 6         10 my $pat;
576              
577 6 100 66     55 if (3 == length $c) {
    100 33        
    50          
    50          
    50          
    0          
578 4         9 my @c = split('', $c);
579 4 50       10 if ($method eq 'fn_c') {
580 4         296 $pat = qr/^(?:
581             [$VOWELS]* $c[0] [$VOWELS]*
582             [$CONSONANTS] [$VOWELS]*
583             $c[1] [$VOWELS]*
584             $c[2] [A-Z]*
585             |
586             [$VOWELS]* $c[0] [$VOWELS]*
587             $c[1] [$VOWELS]*
588             $c[2] [$VOWELS]*
589             )$/xi;
590             } else {
591 0         0 $pat = qr/^
592             [$VOWELS]* $c[0] [$VOWELS]*
593             $c[1] [$VOWELS]*
594             $c[2] [A-Z]*
595             $/xi;
596             }
597             } elsif (2 == length($c) and 1 == length($v)) {
598 1         6 my @c = split('', $c);
599 1         57 $pat = qr/^(?:
600             $v [$VOWELS]* $c[0] [$VOWELS]* $c[1] [$VOWELS]*
601             |
602             $c[0] $v [$VOWELS]* $c[1] [$VOWELS]*
603             |
604             $c[0] $c[1] $v [$VOWELS]*
605             )$/xi;
606             } elsif (1 == length($c) and 2 == length($v)) {
607 0         0 my @v = split('', $v);
608 0         0 $pat = qr/^(?:
609             $c $v[0] $v[1] [$VOWELS]*
610             |
611             $v[0] $c $v[1] [$VOWELS]*
612             |
613             $v[0] $v[1] [$VOWELS]* $c [$VOWELS]*
614             )$/xi;
615             } elsif (3 == length $v) {
616 0         0 $pat = qr/^ $v [$VOWELS]* $/xi;
617             } elsif (1 == length $x) {
618 1 50       3 if (1 == length($c)) {
619 1         21 $pat = qr/^(?: $c $v | $v $c )$/xi;
620             } else {
621 0         0 $pat = qr/^ $v $/xi;
622             }
623             } elsif (2 == length $x) {
624 0         0 $pat = qr/^ $v $/xi;
625             } else {
626 0         0 $pat = qr/^ .* $/xi;
627             }
628            
629 6         73 return $self->{$attr} = $pat;
630             }
631              
632             sub _fix_name {
633 1     1   6489 $_[1] =~ tr/àÀèéÈÉìÌòÒùÙ/AAEEEEIIOOUU/;
  1     6   2  
  1         20  
  6         22  
634 6         12 $_[1] =~ tr/a-zA-Z//cd;
635             }
636              
637             sub _bmaker {
638 132     132   158 my ($value, $bit_length, $bits2use, $root, $c) = @_;
639 132 100       182 unless (defined $c) {
640 3         5 my $cc = 0;
641 3         5 $c = \$cc;
642             }
643 132   100     169 $bits2use ||= 1;
644 132 100       179 $root = 1 unless defined $root;
645 132   100     163 $bit_length ||= 7;
646              
647 132         138 my $b = 0; my $sum = 0;
  132         130  
648              
649 132         181 while ($root) { # the root function increases the number of encoded chars
650 9         25 $sum = _bmaker($value, $bit_length, $bits2use, 0, $c);
651 9 100       31 return $sum if $$c == $value;
652 6         7 $bits2use++;
653 6 50       11 croak("Something went terribly wrong") if $bits2use > $bit_length;
654             }
655            
656 129         175 while ($b <= ($bit_length - $bits2use)) { #we recursively move the
657             #encoded chars to the left
658 250 100       337 if ($bits2use > 1) {
659 120         185 $sum = _bmaker($value, $bit_length - 1 - $b, $bits2use - 1, 0, $c);
660             } else {
661 130         132 $$c++;
662             }
663             #when we reach the desired value, we sum the "bits" in a number
664             #to be used as a binary bitmap for which chars to substitute
665 250 100       347 return 2 ** ($b+1) * $sum + 2 ** $b if $$c == $value;
666 241         323 $b++;
667             }
668            
669 120         147 return $sum;
670             }
671              
672              
673              
674             {
675              
676             my $count_consonants = eval "sub {\$_[0] =~ tr/$CONSONANTS/$CONSONANTS/}";
677              
678 7     7   161 sub _count_consonants { return $count_consonants->($_[1]) }
679              
680             }
681              
682              
683             sub _croak {
684 0     0     my $self = shift;
685 0           confess @_;
686             }
687              
688             1;
689             __END__