File Coverage

blib/lib/Parser/AAMVA/License.pm
Criterion Covered Total %
statement 9 68 13.2
branch 0 16 0.0
condition 0 12 0.0
subroutine 3 10 30.0
pod 4 7 57.1
total 16 113 14.1


line stmt bran cond sub pod time code
1             package Parser::AAMVA::License;
2              
3             $VERSION = "0.55";
4 1     1   15278 use strict;
  1         1  
  1         38  
5 1     1   4 use warnings;
  1         2  
  1         23  
6 1     1   4 use Carp;
  1         8  
  1         1098  
7              
8             =head1 NAME
9             Parser::AAMVA::License - AAMVA Driver's License Magnetic Stripe Parser
10             =cut
11              
12             my @track1fields = ( 'state', 'city', 'name', 'address' );
13              
14             my @track2fields = ( 'id', 'license', 'exyy', 'exmm', 'bcc', 'byy', 'bmm', 'bdd', 'dlx' );
15              
16             my @track3fields = ('cdsversion', 'jdversion', 'postalcode', 'dlclass',
17             'restrictions', 'endorsements', 'sex', 'height',
18             'weight', 'haircolor', 'eyecolor', 'did'
19             );
20              
21             my $exyear;
22             my $dim;
23              
24             sub haircolor
25             {
26             # Hair Color per Ansi D20
27 0     0 1   my $self = shift;
28 0           my %hc = (
29             'BAL' => 'Bald',
30             'BLK' => 'Black',
31             'BLN' => 'Blond',
32             'BRO' => 'Brown',
33             'GRY' => 'Grey',
34             'RED' => 'Red/Auburn',
35             'SDY' => 'Sandy',
36             'WHI' => 'White',
37             'UNK' => 'Unknown'
38             );
39 0           return $hc{ $self->{'haircolor'} };
40             }
41              
42             sub monthdays
43             {
44 0     0 0   my $year = shift;
45 0           my $month = shift;
46            
47 0 0 0       if ( $month == 4 || $month == 6 || $month == 9 || $month == 11 )
      0        
      0        
48             {
49 0           return 30;
50             }
51 0 0         if ( $month == 2 )
52             {
53 0 0         if ( ( $year / 4 ) == int( $year / 4 ) )
54             {
55 0           return 29;
56             }
57             else
58             {
59 0           return 28;
60             }
61             }
62 0           return 31;
63             }
64              
65             sub parse_magstripe
66             {
67 0     0 1   my $self = shift;
68 0           my $i;
69             my @results;
70 0           my $n;
71            
72 0 0         if ( $self->{'track1'})
73 0           {
74 0           @results = ( $self->{'track1'} =~ m/^\%?([A-Z]{2})([^\^]{1,13})\^?([^\^]{1,35})\^?([^\^]+)\^?\?/i );
75            
76 0           for ( $i = 0 ; $i < scalar @track1fields ; $i++ )
77             {
78 0           $self->{ $track1fields[$i] } = $results[$i];
79             }
80            
81 0           ( $self->{'lname'}, $self->{'fname'}, $self->{'middle'} ) = split( /\$/, $self->{'name'} );
82            
83 0           ( $self->{'address1'}, $self->{'address2'} ) = split(/\$/,$self->{'address'});
84            
85 0           foreach $n ('lname','fname','middle','address1','address2')
86             {
87 0           $self->{$n}=stripblanks($self->{$n});
88             }
89             }
90             else
91             {carp "Track one is empty";}
92            
93 0 0         if ( $self->{'track2'})
94             {
95 0           @results = ( $self->{'track2'} =~ m/;?(6[0-9]{5})([0-9]+)=([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})([^=]{0,5})=\??/i );
96              
97 0           for ( $i = 0 ; $i < scalar @track2fields ; $i++ )
98             {
99 0           $self->{ $track2fields[$i] } = $results[$i];
100             }
101 0           $self->{'license'} .= $self->{'dlx'};
102 0           $self->{'birthyear'}=$self->{'bcc'}.$self->{'byy'};
103 0           $self->{'birthmonth'}=$self->{'bmm'};
104 0           $self->{'birthday'}=$self->{'bdd'};
105 0           $self->{'birthdate'}=sprintf('%4d-%02d-%02d',$self->{'birthyear'},$self->{'birthmonth'},$self->{'birthday'});
106            
107 0           $exyear=2000+($self->{'exyy'}+1);
108            
109 0           $dim = monthdays($exyear, $self->{'bmm'});
110            
111 0 0         if ($self->{'exmm'}==88)
112             {
113 0           $self->{'expdate'}=sprintf('%4d-%02d-%02d',$exyear,$self->{'bmm'},$dim);
114             }
115             else
116             {
117 0           $self->{'expdate'}=sprintf('%4d-%02d-%02d',$self->{'exyy'}+2000,$self->{'exmm'},$dim);
118             }
119             }
120            
121 0 0         if ( $self->{'track3'})
122             {
123 0           @results = ( $self->{'track3'} =~ m/%?([0-9]{1})([0-9]{1})([A-Z0-9 ]{11})([A-Z0-9 ]{2})([A-Z0-9 ]{10})([A-Z0-9 ]{4})([1-2]{1})([0-9]{3})([0-9]{3})([A-Z ]{3})([A-Z ]{3})(.*)\?/i );
124            
125 0           for ( $i = 0 ; $i < scalar @track3fields ; $i++ )
126             {
127 0           $self->{ $track3fields[$i] } = $results[$i];
128             }
129             }
130             }
131              
132             sub stripblanks
133             {
134 0     0 0   my $s=shift;
135 0 0         if ($s)
  0            
136             {$s=~s/ +$//;
137 0           $s=~s/^ +//;
138             }
139 0           return $s;
140             }
141              
142              
143             sub loadtrack
144             {
145 0     0 1   my $self=shift;
146 0           my $trackno=shift;
147 0           my $track=shift;
148            
149 0           $track =~ s/\r|\n//gs;
150 0           $self->{"track$trackno"}=$track;
151             }
152              
153             my %jids=(
154             636033=>'Alabama',
155             636059=>'Alaska',
156             604427=>'American Samoa',
157             636026=>'Arizona',
158             636021=>'Arkansas',
159             636014=>'California',
160             636020=>'Colorado',
161             636006=>'Connecticut',
162             636011=>'Delaware',
163             636043=>'District of Columbia',
164             636010=>'Florida',
165             636055=>'Georgia',
166             636019=>'Guam',
167             636047=>'Hawaii',
168             636050=>'Idaho',
169             636035=>'Illinois',
170             636037=>'Indiana',
171             636018=>'Iowa',
172             636022=>'Kansas',
173             636046=>'Kentucky',
174             636007=>'Louisiana',
175             636041=>'Maine',
176             636003=>'Maryland',
177             636002=>'Massachusetts',
178             636032=>'Michigan',
179             636038=>'Minnesota',
180             636051=>'Mississippi',
181             636030=>'Missouri',
182             636008=>'Montana',
183             636054=>'Nebraska',
184             636049=>'Nevada',
185             636039=>'New Hampshire',
186             636036=>'New Jersey',
187             636009=>'New Mexico',
188             636001=>'New York',
189             636004=>'North Carolina',
190             636034=>'North Dakota',
191             636023=>'Ohio',
192             636058=>'Oklahoma',
193             636029=>'Oregon',
194             636025=>'Pennsylvania',
195             636052=>'Rhode Island',
196             636005=>'South Carolina',
197             636042=>'South Dakota',
198             636027=>'State Dept(USA)',
199             636053=>'Tennessee',
200             636015=>'Texas',
201             636062=>'US Virgin Islands',
202             636040=>'Utah',
203             636024=>'Vermont',
204             636000=>'Virginia',
205             636045=>'Washington',
206             636061=>'West Virginia',
207             636031=>'Wisconsin',
208             636060=>'Wyoming',
209             636028=>'British Columbia',
210             636048=>'Manitoba',
211             636012=>'Ontario',
212             636017=>'New Brunswick',
213             636016=>'Newfoundland',
214             636013=>'Nova Scotia',
215             604426=>'Prince Edward Island',
216             604428=>'Quebec',
217             636044=>'Saskatchewan',
218             604429=>'Yukon',
219             636056=>'Coahuila',
220             636057=>'Hidalgo '
221             );
222              
223             sub country
224             {
225 0     0 1   my $self=shift;
226 0           return $jids{$self->{'id'}};
227             }
228              
229             my %country=(
230             636033=>'USA',
231             636059=>'USA',
232             604427=>'USA',
233             636026=>'USA',
234             636021=>'USA',
235             636014=>'USA',
236             636020=>'USA',
237             636006=>'USA',
238             636011=>'USA',
239             636043=>'USA',
240             636010=>'USA',
241             636055=>'USA',
242             636019=>'USA',
243             636047=>'USA',
244             636050=>'USA',
245             636035=>'USA',
246             636037=>'USA',
247             636018=>'USA',
248             636022=>'USA',
249             636046=>'USA',
250             636007=>'USA',
251             636041=>'USA',
252             636003=>'USA',
253             636002=>'USA',
254             636032=>'USA',
255             636038=>'USA',
256             636051=>'USA',
257             636030=>'USA',
258             636008=>'USA',
259             636054=>'USA',
260             636049=>'USA',
261             636039=>'USA',
262             636036=>'USA',
263             636009=>'USA',
264             636001=>'USA',
265             636004=>'USA',
266             636034=>'USA',
267             636023=>'USA',
268             636058=>'USA',
269             636029=>'USA',
270             636025=>'USA',
271             636052=>'USA',
272             636005=>'USA',
273             636042=>'USA',
274             636027=>'USA',
275             636053=>'USA',
276             636062=>'USA',
277             636040=>'USA',
278             636024=>'USA',
279             636000=>'USA',
280             636045=>'USA',
281             636061=>'USA',
282             636031=>'USA',
283             636060=>'USA',
284             636028=>'CAN',
285             636048=>'CAN',
286             636012=>'CAN',
287             636017=>'CAN',
288             636016=>'CAN',
289             636013=>'CAN',
290             604426=>'CAN',
291             604428=>'CAN',
292             636044=>'CAN',
293             604429=>'CAN',
294             636056=>'MEX',
295             636057=>'MEX'
296             );
297              
298             sub new
299             {
300 0     0 0   my $that = shift;
301 0   0       my $class = ref($that) || $that;
302 0           my $self = {
303             track1=>undef,
304             track2=>undef,
305             track3=>undef
306             };
307 0           bless $self, $class;
308 0           return $self;
309             };
310              
311             1;
312              
313             =head1 DESCRIPTION
314              
315             License is a parser/decoder for the American Association of Motor Vehicle Administrators(AAMVA) format that is used to encode the magnetic stripe found on Driver's Licenses in the US and Canada. Most data is available both in its raw and decoded form. You should refer to the latest specification at
316             www.aamva.org for details on the field contents. Starting and ending sentinals in the track data are optional.
317              
318             Load the tracks you have available, only track 1 is mandatory. Not all magnetic stripe readers can read track 3.
319              
320             =head1 SYNOPSIS
321              
322             $track1='%ORSPRINGFIELD ^ SIMPSON $HOMER$J ^ 742 EVERGREEN TERR ^?'; # % and ? are sentinals.
323             $track2 =';6360291234567890123=180119550512=?'; # ; and ? are sentinals.
324            
325             $p = new Parse::AAMVA::License;
326            
327             $p->loadtrack( 1, $track1 );
328             $p->loadtrack( 2, $track2 );
329             $p->loadtrack( 3, $track3 );
330            
331             $p->parse_magstripe;
332              
333             =head1 FIELDS
334              
335             Field Name AAMVA Field # Track
336            
337             state State/Province 2 1
338             city City Name 3 1
339             fname First Name 4 1
340             lname Last Name 4 1
341             middle Middle Name 4 1
342             address1 Address Line 1 5 1
343             address2 Address Line 2 5 1
344             id ISO Id 2 2
345             license License Number 3/7 2 Includes overflow from field 7
346             expdate Expiration 5 2 YYYY-MM-DD (1)
347             birthdate Birth Date 6 2 YYYY-MM-DD
348             cdsversion CDS Version 2 3
349             jdversion Juris. Version 3 3
350             postalcode Postal/Zip Code 4 3
351             dlclass License Class 5 3
352             restrictions Lic. Restrictions 6 3
353             endorsements Lic. Endorsements 7 3
354             sex Sex 1=Male, 2=Female 8 3
355             height Height in in. or cm. 9 3 (2)
356             weight Weight in lbs or kg. 10 3 (2)
357             haircolor Hair Color 11 3 (3)
358             eyecolor Eye Color 12 3
359             did Optionally defined by
360             jurisdiction 13,14,15 3
361              
362             (1). If expiration date year is 2077, license never expires.
363             Otherwise, this field represents the last valid date.
364              
365             (2). Height is in inches or cm depending on country.
366             Weight is in pounds or kg. Call the country method
367             to determine country of origin: USA, MEX or CAN.
368              
369             (3). This the raw hair color, e.g. BRO. For a description,
370             call the haircolor method.
371              
372             =head1 METHODS
373              
374             =over 12
375              
376             =item $p->loadtrack(tracknumber,string);
377              
378             Load the magnetic stripe track
379              
380             =item $p->parse_magstripe
381              
382             Parse loaded magstripe data. Returns null.
383            
384             =item $p->country
385              
386             Attempts to determine country of origin from the ISO ID. Returns CAN, MEX or USA.
387            
388             =item $p->haircolor
389              
390             Returns the ANSI D20 hair color definition
391             BAL Bald
392             BLK Black
393             BLN Blond
394             BRO Brown
395             GRY Grey
396             RED Red/Auburn
397             SDY Sandy
398             WHI White
399             UNK Unknown
400            
401             =back
402              
403             =head1 ACCESSING FIELDS
404              
405             Decoded fields are accessed directly via an anonymous hash:
406             Ex. print $p->{license};
407              
408             =head1 EXAMPLE
409              
410             $p=new Parse:AAMVA:License;
411             $p->loadtrack(1,$track1);
412             $p->loadtrack(2,$track2);
413             $p->loadtrack(3,$track3);
414             $p->parse_magstripe;
415              
416             print "Name: ".$p->{fname}.' '.$p->{lname};
417             print "Birth Date: ".$p->{birthdate};
418              
419             =head1 AUTHOR
420             Curt Evans, C<< >>
421              
422             =head1 COPYRIGHT
423              
424             Copyright Curt Evans, 2014.
425             This program is free software; you can redistribute it and/or
426             modify it under the terms of either:
427             a) the GNU General Public License;
428             either version 2 of the License, or (at your option) any later
429             version. You should have received a copy of the GNU General
430             Public License along with this program; see the file COPYING.
431             If not, write to the Free Software Foundation, Inc., 59
432             Temple Place, Suite 330, Boston, MA 02111-1307 USA
433             b) the Perl Artistic License.
434             This program is distributed in the hope that it will be useful,
435             but WITHOUT ANY WARRANTY; without even the implied warranty of
436             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
437             =cut