File Coverage

lib/Finance/MICR/LineParser.pm
Criterion Covered Total %
statement 217 247 87.8
branch 97 130 74.6
condition 30 34 88.2
subroutine 32 34 94.1
pod 22 24 91.6
total 398 469 84.8


line stmt bran cond sub pod time code
1             package Finance::MICR::LineParser;
2 1     1   782 use strict;
  1         2  
  1         38  
3 1     1   6 use Carp;
  1         2  
  1         174  
4 1     1   16 use warnings;
  1         2  
  1         2970  
5             our $VERSION = sprintf "%d.%02d", q$Revision: 1.7 $ =~ /(\d+)/g;
6              
7             sub new {
8 7     7 1 888 my ($class,$self) = (shift,shift);
9 7   50     17 $self ||= {};
10 7 50       15 $self->{string} or croak('missing string argument to constructor');
11            
12 7   100     27 $self->{transit_symbol} ||='T';
13 7   100     19 $self->{on_us_symbol} ||='U';
14 7   50     23 $self->{ammount_symbol} ||='X';
15 7   50     20 $self->{dash_symbol} ||='D';
16            
17 7   50     21 $self->{max_clean_runs} ||= 6;
18            
19 7         15 bless $self, $class;
20              
21 7         23 $self->{original_string} = $self->{string};
22              
23 7   100     19 until( $self->_match or $self->giveup) {
24 26         44 $self->_clean_string; # returns 0 if it should not clean anymore
25             }
26              
27             # $self->{return_my_symbols} ||=0; # later
28              
29 7         18 return $self;
30             }
31              
32             sub tolerant {
33 3     3 0 4 my $self = shift;
34 3 50       8 (defined $self->{tolerant}) or ($self->{tolerant} = 1); # could be 0
35 3         7 return $self->{tolerant};
36             }
37              
38              
39             sub _match {
40 33     33   34 my $self = shift;
41 33 100       43 if ($self->_business_check_match){ return 1;} # will not run if max_clean_runs HAS been reached
  2         7  
42 31 100       49 if ($self->_personal_check_match){ return 1;} # will not run if max_clean_runs HAS been reached
  2         5  
43 29 100       40 if ($self->_unknown_check_match ){ return 1;} # will not run if max_clean_runs has NOT been reached & tolerant is true
  2         4  
44 27         80 return 0;
45             }
46              
47              
48             sub clean_runs {
49 148     148 1 120 my $self = shift;
50 148   100     237 $self->{_clean_runs} ||= 0;
51 148         305 return $self->{_clean_runs};
52             }
53              
54             sub max_clean_runs {
55 55     55 0 48 my $self = shift;
56 55         124 return $self->{max_clean_runs};
57             }
58              
59              
60             sub _clean_string {
61 26     26   20 my $self = shift;
62            
63 26 50       32 if( $self->clean_runs > $self->max_clean_runs ){
64 0         0 return 0;
65             }
66              
67 26 100       36 if ($self->clean_runs == 0){
    100          
    100          
    100          
    100          
68 5         10 my ($u,$t,$a,$d) = ($self->{on_us_symbol}, $self->{transit_symbol}, $self->{ammount_symbol}, $self->{dash_symbol});
69 5 100       18 if ($t ne 'T'){
70 1         22 $self->{string}=~s/$t/T/g;
71             }
72 5 100       8 if ($u ne 'U'){
73 1         9 $self->{string}=~s/$u/U/g;
74             }
75 5 50       10 if ($a ne 'X'){
76 0         0 $self->{string}=~s/$a/X/g;
77             }
78 5 50       11 if ($d ne 'D'){
79 0         0 $self->{string}=~s/$d/D/g;
80             }
81             }
82            
83             elsif ($self->clean_runs == 1){
84 4         25 $self->{string} =~s/\s+//g;
85             }
86              
87             elsif ($self->clean_runs == 2){
88 4         18 $self->{string} =~s/_|\-//g;
89             }
90              
91             elsif ($self->clean_runs == 3){
92 4         42 $self->{string} =~s/[^0123456789TUXD]//g;
93             }
94              
95             elsif ($self->clean_runs == 4){
96 3 50       11 if( $self->{string}=~m/(T[0123456789TUXD]+U)/ ){ # business check # realize if there's a trailing X (ammount) this will fail
    0          
97 3         6 $self->{string} = $1;
98             }
99             elsif ( $self->{string}=~m/(U[0123456789TUXD]+U[0123456789TUXD]*)/){
100 0         0 $self->{string} = $1;
101             }
102             }
103              
104 26         33 $self->{_clean_runs}++;
105 26         46 return 1;
106             }
107              
108              
109             sub giveup {
110 34     34 1 31 my $self = shift;
111 34   100     91 $self->{_giveup} ||=0;
112 34         85 return $self->{_giveup};
113             }
114              
115              
116              
117              
118              
119             # -------------------------------------------
120             # BEGIN MAIN MATCHERS
121             # ALL MAIN MATCHING IS DONE IN THESE TWO SUBS
122             # if they both fail continuosly, then we dont assume to
123             # succeed
124              
125             sub _business_check_match {
126 33     33   30 my $self= shift;
127 33         37 my $string = $self->{string};
128            
129 33 100       85 if($string =~/(U[\dD]+U)[\-_\s]*(T\d{9}T)[\-_\s]*([\dD ]+U[\-_\s]*[\dD ]*)/){
130 2         6 $self->_set_check_type('business');
131 2         5 $self->{auxiliary_on_us} = $1; $self->{auxiliary_on_us}=~s/\s{2,}/ /g;
  2         5  
132 2         5 $self->{transit} = $2; $self->{transit}=~s/\s{2,}/ /g;
  2         4  
133 2         4 $self->{on_us} = $3; $self->{on_us}=~s/\s{2,}/ /g;
  2         4  
134 2         7 return 1;
135             }
136 31         58 return 0;
137             }
138              
139             sub _personal_check_match {
140 31     31   27 my $self= shift;
141 31         35 my $string = $self->{string};
142            
143             #insist that a valid personal check have trailing digits after the U
144 31 100       85 if ($string =~/(T\d{9}T)[\-_\s]*([\dD ]+U\s*[\dD ]+)/){
145 2         8 $self->_set_check_type('personal');
146 2         4 $self->{transit} = $1; $self->{transit}=~s/\s{2,}/ /g;
  2         5  
147 2         4 $self->{on_us} = $2; $self->{on_us}=~s/\s{2,}/ /g;
  2         3  
148 2         5 return 1;
149             }
150 29         52 return 0;
151             }
152              
153             # aka hacky match
154             sub _unknown_check_match {
155 29     29   27 my $self = shift;
156              
157             # what conditions does this run on
158             # first off, there can not be a match type already
159 29 50       36 if($self->get_check_type) { return; }
  0         0  
160              
161             # second, we must have already exhausted the clean runs we can make
162             # otherwise , it will attempt to match an unclean string before
163             # the other match methods get a chance to succeed.
164             # clean_runs can be less then max_clean_runs, but only if a check type
165             # match was already established, thus, we would not get here at all
166 29 100       49 ($self->clean_runs > $self->max_clean_runs) or return;
167              
168             # third, tolerant flag must be set to true
169             # also, _unknown_check_match() is a last resource, so set giveup flag if not tolerant.
170 3 50       6 unless ($self->tolerant){ $self->{_giveup} = 1; return; }
  0         0  
  0         0  
171              
172              
173            
174             # reset the string
175 3         6 $self->{string} = $self->{original_string};
176            
177              
178            
179 3         18 my ($u,$t,$a,$d) = ($self->{on_us_symbol}, $self->{transit_symbol}, $self->{ammount_symbol}, $self->{dash_symbol});
180 3 50       61 if ($t ne 'T'){
181 0         0 $self->{string}=~s/$t/T/g;
182             }
183 3 50       11 if ($u ne 'U'){
184 0         0 $self->{string}=~s/$u/U/g;
185             }
186 3 50       6 if ($a ne 'X'){
187 0         0 $self->{string}=~s/$a/X/g;
188             }
189 3 50       4 if ($d ne 'D'){
190 0         0 $self->{string}=~s/$d/D/g;
191             }
192            
193            
194            
195 3         4 my $match=0;
196 3         3 my $string = $self->{string}; # TODO: MAKE SURE - is this *really* a copy of what is in self??????
197             # because we are hacking away at it here..
198              
199 3 50       12 if ($string=~s/(U[\dD]+U)//) { #aux on us
200 0         0 $self->{auxiliary_on_us} = $1;
201 0         0 $match++;
202             }
203              
204 3 100       10 if ($string=~s/(T\d{9}T)//){
205 2         5 $self->{transit}= $1;
206 2         2 $match++;
207             }
208              
209 3 50       15 if ($string=~/[^U](\d{5,18}U[\dD]*)/ ) { # how many digits can this be? can the account number have dashes?
210 0         0 $self->{on_us} = $1;
211 0         0 $match++;
212             }
213              
214            
215             # we want to assert that if we resorted to *this* method of matching fields, this is was a crappy string
216             # to begin with.
217              
218 3 100       5 if( $match ){
219 2         5 $self->_set_check_type('unknown');
220 2         5 return 1; # return $match;
221             }
222            
223             # _unknown_check_match() is a last resource, so set giveup flag since there was
224             # no match by this point
225 1         2 $self->{_giveup} = 1;
226 1         3 return 0;
227             }
228              
229              
230              
231              
232              
233              
234              
235             # END MAIN MATCHERS
236             # -------------------------------------------
237              
238              
239              
240              
241              
242              
243              
244             # summary MICR string info
245              
246             sub micr {
247 20     20 1 18 my $self = shift;
248             #$self->valid or return;
249              
250             # my $micr = $self->{string};
251              
252             # $micr=~s/\s|\-|_//g;
253 20         17 my $micr;
254              
255 20 100       30 if ($self->is_business_check){
    100          
    100          
256 4         6 $micr .= $self->auxiliary_on_us . $self->transit . $self->on_us;
257             }
258            
259             elsif ($self->is_personal_check){
260 8         15 $micr .= $self->transit . $self->on_us;
261             }
262              
263             elsif ($self->is_unknown_check){ # assume / guess that it's a business check
264 6 50       10 $micr .= $self->auxiliary_on_us ? $self->auxiliary_on_us : 'UxxxxxxU';
265 6 50       10 $micr .= $self->transit ? $self->transit : 'TxxxxxxT';
266 6 50       17 $micr .= $self->on_us ? $self->on_us : 'xxxxxxU';
267             }
268             else {
269 2         5 return;
270             }
271              
272 18         51 return $micr;
273             }
274              
275             sub micr_pretty {
276 10     10 1 10 my $self = shift;
277 10 100       14 my $micr = $self->micr or return;
278 9         12 $micr=~s/ /_/g;
279 9         12 for( $micr ) {
280 9         70 s/([^[:^alpha:]xuUD])([\dxuDU]+\1)/_$1$2_/g;
281 9         84 s/(?<=([^\dxuUD_]))?([\dxuDU]+)([^\dxuDU_])/
282 9 50       130 $2 . $3 . ( $1 eq $3 ? '' : '_' )
283             /ge;
284 9         34 s/_(_|$)/$1/g;
285             }
286            
287 9         81 $micr=~s/^_+|_{2,}|_+$//g;
288 9         29 return $micr;
289             }
290              
291              
292             sub original_string {
293 0     0 1 0 my $self-> shift;
294 0         0 return $self->{orginal_string};
295             }
296              
297             sub is_business_check {
298 84     84 1 79 my $self = shift;
299 84   100     230 $self->{is_business_check} ||=0;
300 84         195 return $self->{is_business_check};
301             }
302             sub is_personal_check{
303 78     78 1 79 my $self = shift;
304 78   100     182 $self->{is_personal_check} ||=0;
305 78         178 return $self->{is_personal_check};
306             }
307              
308             sub is_unknown_check {
309 46     46 1 42 my $self = shift;
310 46   100     94 $self->{is_unknown_check} ||=0;
311 46         101 return $self->{is_unknown_check};
312             }
313              
314             sub get_check_type {
315 41     41 1 41 my $self = shift;
316 41 100       52 !$self->is_business_check or return 'b';
317 38 100       56 !$self->is_personal_check or return 'p';
318 34 100       47 !$self->is_unknown_check or return 'u';
319 30         44 return;
320             }
321              
322             sub _set_check_type {
323 6     6   7 my $self = shift;
324 6 50       5 my $type = shift; $type or croak('missing arg to _set_check_type()');
  6         12  
325 6 50       18 $type=~/^b|^p|^u/i or croak("type:[$type] unrecognized type, use (u)nknown, (b)usiness, or (p)ersonal");
326            
327 6   100     22 $self->{is_business_check} = ($type=~/^b/i or 0);
328 6   100     25 $self->{is_personal_check} = ($type=~/^p/i or 0);
329 6   100     20 $self->{is_unknown_check} = ($type=~/^u/i or 0);
330 6         9 return 1;
331             }
332              
333              
334              
335              
336             # main MICR METHODS
337             # the five major fields
338              
339             sub auxiliary_on_us {
340 14     14 1 15 my $self = shift;
341 14 100       37 defined $self->{auxiliary_on_us} or return;
342 5         12 return $self->{auxiliary_on_us};
343             }
344              
345             sub on_us {
346 43     43 1 55 my $self = shift;
347 43 100       97 defined $self->{on_us} or return;
348 26         73 return $self->{on_us};
349             }
350              
351              
352              
353              
354             # ----------------------------------
355             # transit and subfields
356              
357             sub transit {
358 64     64 1 52 my $self = shift;
359 64 100       190 defined $self->{transit} or return;
360 58 100       101 if (defined $self->{check_digit}){ return $self->{transit}; }
  52         118  
361              
362 6         7 my $transit = $self->{transit};
363             #$transit=~s/T//g;
364 6 50       26 $transit=~/T(\d{1})(\d{4})(\d{4})T$/ or die("transit() returns messed up:[$transit], should begin with the transit symbol, have 9 digits, and end in the transit symbol.");
365            
366 6         14 $self->{check_digit} = $1;
367 6         17 $self->{bank_number} = $2;
368 6         10 $self->{routing_number} = $3;
369            
370 6         18 return $self->{transit};
371             }
372              
373             sub check_digit {
374 7     7 1 6 my $self = shift;
375 7 100       12 $self->transit or return;
376 6         14 return $self->{check_digit};
377             }
378              
379              
380             sub bank_number {
381 7     7 1 7 my $self = shift;
382 7 100       18 $self->transit or return;
383 6         14 return $self->{bank_number};
384             }
385              
386             sub routing_number {
387 14     14 1 607 my $self = shift;
388 14 100       18 $self->transit or return;
389 12         35 return $self->{routing_number};
390             }
391              
392              
393             # end transit and subfields
394              
395              
396              
397             # TODO: this is not fully functional
398             sub epc {
399 7     7 1 6 my $self = shift;
400 7 50       20 defined $self->{epc} or return;
401 0         0 return $self->{epc};
402             }
403              
404             # TODO: this is not fully functional
405             sub ammount {
406 0     0 1 0 my $self = shift;
407 0 0       0 defined $self->{ammount} or return;
408 0         0 return $self->{ammount};
409             }
410              
411              
412              
413              
414              
415              
416             sub account_number {
417 7     7 1 7 my $self = shift;
418 7 50       13 if (defined $self->{account_number}){ return $self->{account_number}; }
  0         0  
419              
420 7 100       11 $self->on_us or return; # if not defined, we cant extract bank number
421            
422 3 50       6 if ( $self->on_us=~/([\dD ]{5,19})U/ ){
423 3         5 $self->{account_number} = $1;
424 3         10 return $self->{account_number};
425             }
426 0         0 return;
427             }
428              
429              
430              
431             sub check_number {
432 10     10 1 20 my $self = shift;
433 10 100       21 if( defined $self->{check_number}){ return $self->{check_number}; }
  3         14  
434             # a lot of this is sort of redundant, but i put it here for reasoning for a viewer
435            
436             # check number may be on the on us or in the aux on us
437            
438 7 100       10 if ($self->is_business_check){
    100          
439 1         3 $self->{check_number} = $self->auxiliary_on_us;
440 1         3 $self->{check_number}=~s/U//g;
441             #$self->{check_number}=~s/^0+//;
442 1         4 return $self->{check_number};
443             }
444             elsif ($self->is_personal_check){
445 2 50       3 if ($self->on_us=~/U\s*(\d+)/ ){
446 2         4 $self->{check_number} = $1;
447             #$self->{check_number}=~s/^0+//;
448 2         4 return $self->{check_number};
449             }
450             else {
451 0         0 return; # personal check with no check number
452             }
453             }
454              
455            
456 4 100       8 if ($self->is_unknown_check){
457            
458             # at this point the string is not a valid() MICR check string .. the
459             # procedures are sameish.. but we made sure type is unknown
460 3 50       5 if ($self->auxiliary_on_us){
    50          
461 0         0 $self->{check_number} = $self->auxiliary_on_us;
462 0         0 $self->{check_number}=~s/U//g;
463             #$self->{check_number}=~s/^0+//;
464 0         0 return $self->{check_number};
465             }
466              
467             elsif ($self->on_us){
468 0 0       0 if ($self->on_us=~/U([\d]+)/ ){
469 0         0 $self->{check_number} = $1;
470             #$self->{check_number}=~s/^0+//;
471 0         0 return $self->{check_number};
472             }
473             }
474             }
475            
476 4         6 return;
477             }
478              
479              
480              
481              
482              
483             sub valid {
484 11     11 1 18 my $self = shift;
485 11 100 100     15 if ($self->is_personal_check or $self->is_business_check){
486 4         14 return 1;
487             }
488 7         18 return 0;
489             }
490              
491              
492             sub status {
493 7     7 1 14 my $self = shift;
494 1     1   10 no warnings;
  1         2  
  1         294  
495              
496            
497 7         21 my $out = sprintf "original string [%s]\n",$self->{original_string};
498 7         13 $out.= sprintf "runs: [%s]\n", $self->clean_runs;
499 7         16 $out.= sprintf "string: [%s]\n", $self->{string};
500 7         13 $out.= sprintf"giveup: [%s]\n", $self->giveup;
501 7         13 $out.= sprintf"transit: [%s]\n", $self->transit;
502 7         14 $out.= sprintf"on_us: [%s]\n", $self->on_us;
503 7         14 $out.= sprintf"account #: [%s]\n", $self->account_number ;
504 7         10 $out.= sprintf"check #: [%s]\n", $self->check_number;
505 7         14 $out.= sprintf"is personal: [%s]\n", $self->is_personal_check;
506 7         13 $out.= sprintf"is business: [%s]\n", $self->is_business_check;
507 7         14 $out.= sprintf"bank_number: [%s]\n", $self->bank_number;
508 7         12 $out.= sprintf"routing number: [%s]\n", $self->routing_number;
509 7         15 $out.= sprintf"epc: [%s]\n", $self->epc;
510 7         10 $out.= sprintf"check_digit: [%s]\n", $self->check_digit;
511 7         13 $out.= sprintf"is valid: [%s]\n", $self->valid;
512 7         12 $out.= sprintf"check type: [%s]\n", $self->get_check_type;
513 7         14 $out.= sprintf"micr: [%s]\n", $self->micr;
514 7         15 $out.= sprintf"micr_pretty: [%s]\n", $self->micr_pretty;
515            
516            
517 7         144 return $out;
518             }
519              
520              
521              
522              
523             1;
524             __END__