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 0 7 0.0
total 12 113 10.6


line stmt bran cond sub pod time code
1             package Parser::AAMVA::License;
2              
3             $VERSION = "0.50";
4 1     1   22509 use strict;
  1         3  
  1         54  
5 1     1   7 use warnings;
  1         2  
  1         41  
6 1     1   7 use Carp;
  1         10  
  1         1254  
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 0   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 0   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 0   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 0   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             License is a parser/decoder for the American Association of Motor Vehicle Administrators(AAMVA) format
315             that is used to encode the magnetic stripe found on Driver's Licenses in the US and Canada.
316             Most data is available both in its raw and decoded form. You should refer to the latest specification at
317             www.aamva.org for details on the field contents.
318             Starting and ending sentinals in the track data are optional.
319              
320             Load the tracks you have available. Not all magnetic stripe readers can read track 3.
321             $track1='%ORSPRINGFIELD ^ SIMPSON $HOMER$J ^ 742 EVERGREEN TERR ^?'; # % and ? are sentinals .
322             $track2 =';6360291234567890123=180119550512=?'; # ; and ? are sentinals.
323             $p = new Parse::AAMVA::License;
324             $p->loadtrack( 1, $track1 );
325             $p->loadtrack( 2, $track2 );
326             $p->loadtrack( 3, $track3 );
327             $p->parse_magstripe;
328              
329             =back
330             =head1 FIELDS
331             Field Name AAMVA Field # Track
332             state State/Province 2 1
333             city City Name 3 1
334             fname First Name 4 1
335             lname Last Name 4 1
336             middle Middle Name 4 1
337             address1 Address Line 1 5 1
338             address2 Address Line 2 5 1
339             id ISO Id 2 2
340             license License Number 3/7 2 Includes overflow from field 7
341             expdate Expiration 5 2 YYYY-MM-DD (1)
342             birthdate Birth Date 6 2 YYYY-MM-DD
343             cdsversion CDS Version 2 3
344             jdversion Juris. Version 3 3
345             postalcode Postal/Zip Code 4 3
346             dlclass License Class 5 3
347             restrictions Lic. Restrictions 6 3
348             endorsements Lic. Endorsements 7 3
349             sex Sex 1=Male, 2=Female 8 3
350             height Height in in. or cm. 9 3 (2)
351             weight Weight in lbs or kg. 10 3 (2)
352             haircolor Hair Color 11 3 (3)
353             eyecolor Eye Color 12 3
354             did Optionally defined
355             by jusrisdiction
356             13,14,15 3
357              
358             (1). If expiration date year is 2077, license never expires.
359             Otherwise, this field represents the last valid date.
360             (2). Height is in inches or cm depending on country.
361             Weight is in pounds or kg. Call the country method
362             to determine country of origin: USA, MEX or CAN.
363             (3). This the raw hair color, e.g. BRO. For a description,
364             call the haircolor method.
365             =back
366             =head1 METHODS
367             =item $p->loadtrack(trackno,string);
368             Load the magnetic stripe track
369             =item $p->parse_magstripe
370             Parse loaded magstripe data. Returns null.
371             =item $p->country
372             Attempts to determine country of origin from the
373             ISO ID. Returns CAN, MEX or USA.
374             =item $p->haircolor
375             Returns the ANSI D20 hair color definition
376             BAL Bald
377             BLK Black
378             BLN Blond
379             BRO Brown
380             GRY Grey
381             RED Red/Auburn
382             SDY Sandy
383             WHI White
384             UNK Unknown
385             =back
386             =head1 ACCESSING FIELDS
387             Decoded fields are accessed directly via an anonymous hash:
388             Ex. print $p->{license};
389             =head1 EXAMPLE
390             $p=new Parse:AAMVA:License;
391             $p->loadtrack(1,$track1);
392             $p->loadtrack(2,$track2);
393             $p->loadtrack(3,$track3);
394             $p->parse_magstripe;
395              
396             print "Name: ".$p->{fname}.' '.$p->{lname};
397             print "Birth Date: ".$p->{birthdate};
398             =back
399             =head1 AUTHOR
400             Curt Evans, C<< >>
401             =back
402             =head1 COPYRIGHT
403             Copyright Curt Evans, 2014.
404             This program is free software; you can redistribute it and/or
405             modify it under the terms of either:
406             a) the GNU General Public License;
407             either version 2 of the License, or (at your option) any later
408             version. You should have received a copy of the GNU General
409             Public License along with this program; see the file COPYING.
410             If not, write to the Free Software Foundation, Inc., 59
411             Temple Place, Suite 330, Boston, MA 02111-1307 USA
412             b) the Perl Artistic License.
413             This program is distributed in the hope that it will be useful,
414             but WITHOUT ANY WARRANTY; without even the implied warranty of
415             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
416             =back