line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Data::Validate::VIN; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
137252
|
use 5.008; |
|
2
|
|
|
|
|
18
|
|
4
|
2
|
|
|
2
|
|
10
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
42
|
|
5
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
5335
|
|
6
|
|
|
|
|
|
|
#use Carp; |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
sub new { |
11
|
12
|
|
|
12
|
1
|
7322
|
my ( $class, $vin ) = @_; |
12
|
|
|
|
|
|
|
|
13
|
12
|
|
|
|
|
27
|
my $self = bless {}, $class; |
14
|
|
|
|
|
|
|
|
15
|
12
|
|
|
|
|
49
|
$self->{_allowed} = qr/[A-HJ-NPR-Z0-9]/; |
16
|
12
|
|
|
|
|
25
|
$self->{errors} = []; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# this next one might add to $self->{errors}, which is fine |
19
|
12
|
|
|
|
|
28
|
$self->{vin} = $self->_checkVIN($vin); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
# we won't process the wmi, vds or vis if the vin check returned errors |
22
|
12
|
100
|
|
|
|
17
|
unless ( scalar( @{ $self->{errors} } ) > 0 ) { |
|
12
|
|
|
|
|
34
|
|
23
|
3
|
|
|
|
|
11
|
$self->{wmi} = $self->_checkWMI( $self->{vin} ); |
24
|
3
|
|
|
|
|
12
|
$self->{vds} = $self->_checkVDS( $self->{vin} ); |
25
|
3
|
|
|
|
|
11
|
$self->{vis} = $self->_checkVIS( $self->{vin} ); |
26
|
3
|
|
|
|
|
9
|
$self->{checkdigit} = $self->_checkCheckDigit( $self->{vin} ); |
27
|
|
|
|
|
|
|
} |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$self->{valid} = |
30
|
12
|
100
|
|
|
|
20
|
scalar( @{ $self->{errors} } > 0 ) |
|
12
|
|
|
|
|
34
|
|
31
|
|
|
|
|
|
|
? undef |
32
|
|
|
|
|
|
|
: 1; |
33
|
|
|
|
|
|
|
|
34
|
12
|
|
|
|
|
36
|
return $self; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
sub valid { |
38
|
3
|
|
|
3
|
1
|
708
|
my ($self) = @_; |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
defined $self->{valid} |
41
|
3
|
100
|
|
|
|
24
|
? return 1 |
42
|
|
|
|
|
|
|
: return; |
43
|
|
|
|
|
|
|
} |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
sub errors { |
46
|
12
|
|
|
12
|
1
|
4745
|
my ($self) = @_; |
47
|
|
|
|
|
|
|
|
48
|
12
|
|
|
|
|
49
|
scalar( @{ $self->{errors} } > 0 ) |
49
|
|
|
|
|
|
|
? return $self->{errors} |
50
|
12
|
100
|
|
|
|
19
|
: return []; |
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub get { |
54
|
24
|
|
|
24
|
1
|
13055
|
my ( $self, $wanted ) = @_; |
55
|
|
|
|
|
|
|
|
56
|
24
|
50
|
|
|
|
163
|
if ( $wanted =~ /wmi|vds|vis|vin|checkdigit|country|year/i ) { |
57
|
24
|
100
|
|
|
|
110
|
if ( $wanted =~ /vin|checkdigit/i ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
58
|
|
|
|
|
|
|
defined $self->{$wanted} |
59
|
8
|
100
|
|
|
|
49
|
? return $self->{$wanted} |
60
|
|
|
|
|
|
|
: return; |
61
|
|
|
|
|
|
|
} |
62
|
|
|
|
|
|
|
elsif ( $wanted =~ /wmi|vds|vis/i ) { |
63
|
|
|
|
|
|
|
defined $self->{$wanted}->{$wanted} |
64
|
12
|
100
|
|
|
|
78
|
? return $self->{$wanted}->{$wanted} |
65
|
|
|
|
|
|
|
: return; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
elsif ( $wanted =~ /country/i ) { |
68
|
|
|
|
|
|
|
defined $self->{wmi}->{$wanted} |
69
|
0
|
0
|
|
|
|
0
|
? return $self->{wmi}->{$wanted} |
70
|
|
|
|
|
|
|
: return; |
71
|
|
|
|
|
|
|
} |
72
|
|
|
|
|
|
|
elsif ( $wanted =~ /year/i ) { |
73
|
|
|
|
|
|
|
defined $self->{vis}->{$wanted} |
74
|
4
|
100
|
|
|
|
26
|
? return $self->{vis}->{$wanted} |
75
|
|
|
|
|
|
|
: return; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
else { |
78
|
0
|
|
|
|
|
0
|
return; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
0
|
|
|
|
|
0
|
return; |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub _checkVIN { |
85
|
12
|
|
|
12
|
|
26
|
my ( $self, $_vin ) = @_; |
86
|
|
|
|
|
|
|
|
87
|
12
|
100
|
100
|
|
|
80
|
if ( not defined $_vin or $_vin =~ /^$/ ) { |
88
|
2
|
|
|
|
|
8
|
$self->_trackError("No VIN supplied"); |
89
|
2
|
|
|
|
|
4
|
return; |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
10
|
|
|
|
|
19
|
chomp($_vin); |
93
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
$_vin = $self->_checkCharacters( |
95
|
|
|
|
|
|
|
wanted => $self->{_allowed}, |
96
|
10
|
|
|
|
|
40
|
unwanted => qr/[IOQ]/, |
97
|
|
|
|
|
|
|
toCheck => $_vin, |
98
|
|
|
|
|
|
|
section => 'VIN' |
99
|
|
|
|
|
|
|
); |
100
|
|
|
|
|
|
|
|
101
|
10
|
100
|
|
|
|
35
|
if ( length($_vin) != 17 ) { |
102
|
6
|
|
|
|
|
27
|
my $err = sprintf( "%- 17s", $_vin ) . " is not the expected length"; |
103
|
6
|
|
|
|
|
14
|
$self->_trackError($err); |
104
|
|
|
|
|
|
|
} |
105
|
|
|
|
|
|
|
|
106
|
10
|
|
|
|
|
20
|
return $_vin; |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub _checkWMI { |
110
|
3
|
|
|
3
|
|
7
|
my ( $self, $_vin ) = @_; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
my $wmi = { |
113
|
|
|
|
|
|
|
wmi => $self->_checkCharacters( |
114
|
|
|
|
|
|
|
wanted => $self->{_allowed}, |
115
|
3
|
|
|
|
|
17
|
unwanted => qr/[IOQ]/, |
116
|
|
|
|
|
|
|
toCheck => substr( $_vin, 0, 2 ), |
117
|
|
|
|
|
|
|
section => 'WMI' |
118
|
|
|
|
|
|
|
), |
119
|
|
|
|
|
|
|
}; |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# load known valid WMIs |
122
|
3
|
|
|
|
|
10
|
my $_allowed = $self->_loadWMI(); |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
defined $_allowed->{ $wmi->{wmi} } |
125
|
|
|
|
|
|
|
? $wmi->{country} = $_allowed->{ $wmi->{wmi} } |
126
|
3
|
100
|
|
|
|
18
|
: $self->_trackError("Unknown WMI: $wmi->{wmi}"); |
127
|
|
|
|
|
|
|
|
128
|
3
|
|
|
|
|
217
|
return $wmi; |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
sub _checkCharacters { |
132
|
19
|
|
|
19
|
|
82
|
my ( $self, %args ) = @_; |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
# wanted unwanted toCheck section |
135
|
|
|
|
|
|
|
|
136
|
19
|
|
|
|
|
46
|
my $checked = uc($args{toCheck}); |
137
|
19
|
|
|
|
|
65
|
my @checked = split(q{}, $checked); ## char array |
138
|
19
|
|
|
|
|
29
|
my @illegal; |
139
|
|
|
|
|
|
|
|
140
|
19
|
|
|
|
|
50
|
for (my $i = 0; $i < @checked; $i++) { |
141
|
155
|
100
|
|
|
|
741
|
unless ($checked[$i] =~ /^$args{wanted}+$/) { |
142
|
8
|
|
|
|
|
26
|
push @illegal, $checked[$i]; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
|
146
|
19
|
100
|
|
|
|
41
|
if (@illegal) { |
147
|
5
|
|
|
|
|
18
|
my $err = "Illegal characters in " . $args{section} . ': ' . join(q{}, @illegal); |
148
|
5
|
|
|
|
|
13
|
$self->_trackError($err); |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
19
|
|
|
|
|
77
|
return $checked; |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
sub _loadWMI { |
155
|
3
|
|
|
3
|
|
9
|
my ($self) = @_; |
156
|
|
|
|
|
|
|
|
157
|
3
|
|
|
|
|
32
|
my %wmi; |
158
|
|
|
|
|
|
|
|
159
|
3
|
|
|
|
|
17
|
$self->_loadCountry( 'AA', 'AH', 'South Africa', \%wmi ); |
160
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'AJ', 'AN', 'Ivory Coast', \%wmi ); |
161
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'BA', 'BE', 'Angola', \%wmi ); |
162
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'BF', 'BK', 'Kenya', \%wmi ); |
163
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'BL', 'BR', 'Tanzania', \%wmi ); |
164
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'CA', 'CE', 'Benin', \%wmi ); |
165
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'CF', 'CK', 'Madagascar', \%wmi ); |
166
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'CL', 'CR', 'Tunisia', \%wmi ); |
167
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'DA', 'DE', 'Egypt', \%wmi ); |
168
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'DF', 'DK', 'Morocco', \%wmi ); |
169
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'DL', 'DR', 'Zambia', \%wmi ); |
170
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'EA', 'EE', 'Ethiopia', \%wmi ); |
171
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'EF', 'EK', 'Mozambique', \%wmi ); |
172
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'FA', 'FE', 'Ghana', \%wmi ); |
173
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'FF', 'FK', 'Nigeria', \%wmi ); |
174
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'JA', 'JT', 'Japan', \%wmi ); |
175
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'KA', 'KE', 'Sri Lanka', \%wmi ); |
176
|
3
|
|
|
|
|
7
|
$self->_loadCountry( 'KF', 'KK', 'Israel', \%wmi ); |
177
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'KL', 'KR', 'Korea (South)', \%wmi ); |
178
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'LA', 'L0', 'China', \%wmi ); |
179
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'MA', 'ME', 'India', \%wmi ); |
180
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'MF', 'MK', 'Indonesia', \%wmi ); |
181
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'ML', 'MR', 'Thailand', \%wmi ); |
182
|
3
|
|
|
|
|
18
|
$self->_loadCountry( 'NF', 'NK', 'Pakistan', \%wmi ); |
183
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'NL', 'NR', 'Turkey', \%wmi ); |
184
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'PA', 'PE', 'Philippines', \%wmi ); |
185
|
3
|
|
|
|
|
13
|
$self->_loadCountry( 'PF', 'PK', 'Singapore', \%wmi ); |
186
|
3
|
|
|
|
|
12
|
$self->_loadCountry( 'PL', 'PR', 'Malaysia', \%wmi ); |
187
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'RA', 'RE', 'United Arab Emirates', \%wmi ); |
188
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'RF', 'RK', 'Taiwan', \%wmi ); |
189
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'RL', 'RR', 'Vietnam', \%wmi ); |
190
|
3
|
|
|
|
|
13
|
$self->_loadCountry( 'SA', 'SM', 'United Kingdom', \%wmi ); |
191
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'SN', 'ST', 'Germany', \%wmi ); |
192
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'SU', 'SZ', 'Poland', \%wmi ); |
193
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'S1', 'S4', 'Latvia', \%wmi ); |
194
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'TA', 'TH', 'Switzerland', \%wmi ); |
195
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'TJ', 'TP', 'Czech Republic', \%wmi ); |
196
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'TR', 'TV', 'Hungary', \%wmi ); |
197
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'TW', 'T1', 'Portugal', \%wmi ); |
198
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'UH', 'UM', 'Denmark', \%wmi ); |
199
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'UN', 'UT', 'Ireland', \%wmi ); |
200
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'UU', 'UZ', 'Romania', \%wmi ); |
201
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'U5', 'U7', 'Slovakia', \%wmi ); |
202
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'VA', 'VE', 'Austria', \%wmi ); |
203
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'VF', 'VR', 'France', \%wmi ); |
204
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'VS', 'VW', 'Spain', \%wmi ); |
205
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'VX', 'V2', 'Serbia', \%wmi ); |
206
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'V3', 'V5', 'Croatia', \%wmi ); |
207
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'V6', 'V0', 'Estonia', \%wmi ); |
208
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'WA', 'W0', 'Germany', \%wmi ); |
209
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'XA', 'XE', 'Bulgaria', \%wmi ); |
210
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'XF', 'XK', 'Greece', \%wmi ); |
211
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'XL', 'XR', 'Netherlands', \%wmi ); |
212
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'XS', 'XW', 'USSR', \%wmi ); |
213
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'XX', 'X2', 'Luxembourg', \%wmi ); |
214
|
3
|
|
|
|
|
10
|
$self->_loadCountry( 'X3', 'X0', 'Russia', \%wmi ); |
215
|
3
|
|
|
|
|
21
|
$self->_loadCountry( 'YA', 'YE', 'Belgium', \%wmi ); |
216
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'YF', 'YK', 'Finland', \%wmi ); |
217
|
3
|
|
|
|
|
11
|
$self->_loadCountry( 'YL', 'YR', 'Malta', \%wmi ); |
218
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'YS', 'YW', 'Sweden', \%wmi ); |
219
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'YX', 'Y2', 'Norway', \%wmi ); |
220
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'Y3', 'Y5', 'Belarus', \%wmi ); |
221
|
3
|
|
|
|
|
9
|
$self->_loadCountry( 'Y6', 'Y0', 'Ukraine', \%wmi ); |
222
|
3
|
|
|
|
|
8
|
$self->_loadCountry( 'ZA', 'ZR', 'Italy', \%wmi ); |
223
|
3
|
|
|
|
|
12
|
$self->_loadCountry( 'ZX', 'Z2', 'Slovenia', \%wmi ); |
224
|
3
|
|
|
|
|
18
|
$self->_loadCountry( 'Z3', 'Z5', 'Lithuania', \%wmi ); |
225
|
3
|
|
|
|
|
11
|
$self->_loadCountry( '1A', '10', 'United States', \%wmi ); |
226
|
3
|
|
|
|
|
10
|
$self->_loadCountry( '2A', '20', 'Canada', \%wmi ); |
227
|
3
|
|
|
|
|
11
|
$self->_loadCountry( '3A', '3W', 'Mexico', \%wmi ); |
228
|
3
|
|
|
|
|
18
|
$self->_loadCountry( '3X', '37', 'Costa Rica', \%wmi ); |
229
|
3
|
|
|
|
|
10
|
$self->_loadCountry( '38', '30', 'Cayman Islands', \%wmi ); |
230
|
3
|
|
|
|
|
8
|
$self->_loadCountry( '4A', '40', 'United States', \%wmi ); |
231
|
3
|
|
|
|
|
14
|
$self->_loadCountry( '5A', '50', 'United States', \%wmi ); |
232
|
3
|
|
|
|
|
13
|
$self->_loadCountry( '6A', '6W', 'Australia', \%wmi ); |
233
|
3
|
|
|
|
|
14
|
$self->_loadCountry( '7A', '7E', 'New Zealand', \%wmi ); |
234
|
3
|
|
|
|
|
8
|
$self->_loadCountry( '8A', '8E', 'Argentina', \%wmi ); |
235
|
3
|
|
|
|
|
8
|
$self->_loadCountry( '8F', '8K', 'Chile', \%wmi ); |
236
|
3
|
|
|
|
|
11
|
$self->_loadCountry( '8L', '8R', 'Ecuador', \%wmi ); |
237
|
3
|
|
|
|
|
8
|
$self->_loadCountry( '8S', '8W', 'Peru', \%wmi ); |
238
|
3
|
|
|
|
|
9
|
$self->_loadCountry( '8X', '82', 'Venezuela', \%wmi ); |
239
|
3
|
|
|
|
|
7
|
$self->_loadCountry( '9A', '9E', 'Brazil', \%wmi ); |
240
|
3
|
|
|
|
|
8
|
$self->_loadCountry( '9F', '9K', 'Colombia', \%wmi ); |
241
|
3
|
|
|
|
|
9
|
$self->_loadCountry( '9L', '9R', 'Paraguay', \%wmi ); |
242
|
3
|
|
|
|
|
11
|
$self->_loadCountry( '9S', '9W', 'Uruguay', \%wmi ); |
243
|
3
|
|
|
|
|
9
|
$self->_loadCountry( '9X', '92', 'Trinidad & Tobago', \%wmi ); |
244
|
3
|
|
|
|
|
9
|
$self->_loadCountry( '93', '99', 'Brazil', \%wmi ); |
245
|
|
|
|
|
|
|
|
246
|
3
|
|
|
|
|
8
|
return \%wmi; |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub _checkVDS { |
250
|
3
|
|
|
3
|
|
8
|
my ( $self, $_vin ) = @_; |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
my $vds = { |
253
|
|
|
|
|
|
|
vds => length($_vin) == 17 |
254
|
|
|
|
|
|
|
? $self->_checkCharacters( |
255
|
|
|
|
|
|
|
wanted => $self->{_allowed}, |
256
|
3
|
50
|
|
|
|
28
|
unwanted => qr/[IOQ]/, |
257
|
|
|
|
|
|
|
toCheck => substr( $_vin, 3, 6 ), |
258
|
|
|
|
|
|
|
section => 'VDS' |
259
|
|
|
|
|
|
|
) |
260
|
|
|
|
|
|
|
: undef |
261
|
|
|
|
|
|
|
}; |
262
|
|
|
|
|
|
|
|
263
|
3
|
|
|
|
|
15
|
return $vds; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
sub _checkVIS { |
267
|
3
|
|
|
3
|
|
10
|
my ( $self, $_vin ) = @_; |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
my $vis = { |
270
|
|
|
|
|
|
|
vis => length($_vin) == 17 |
271
|
|
|
|
|
|
|
? $self->_checkCharacters( |
272
|
|
|
|
|
|
|
wanted => $self->{_allowed}, |
273
|
3
|
50
|
|
|
|
21
|
unwanted => qr/[IOQ]/, |
274
|
|
|
|
|
|
|
toCheck => substr( $_vin, 9, 8 ), |
275
|
|
|
|
|
|
|
section => 'VIS' |
276
|
|
|
|
|
|
|
) |
277
|
|
|
|
|
|
|
: undef |
278
|
|
|
|
|
|
|
}; |
279
|
|
|
|
|
|
|
|
280
|
3
|
50
|
|
|
|
12
|
if ( defined $vis->{vis} ) { |
281
|
|
|
|
|
|
|
|
282
|
3
|
|
|
|
|
60
|
my %years = ( |
283
|
|
|
|
|
|
|
A => [ 1980, 2010 ], |
284
|
|
|
|
|
|
|
L => [ 1990, 2020 ], |
285
|
|
|
|
|
|
|
Y => [ 2000, 2030 ], |
286
|
|
|
|
|
|
|
B => [ 1981, 2011 ], |
287
|
|
|
|
|
|
|
M => [ 1991, 2021 ], |
288
|
|
|
|
|
|
|
1 => [ 2001, 2031 ], |
289
|
|
|
|
|
|
|
C => [ 1982, 2012 ], |
290
|
|
|
|
|
|
|
N => [ 1992, 2022 ], |
291
|
|
|
|
|
|
|
2 => [ 2002, 2032 ], |
292
|
|
|
|
|
|
|
D => [ 1983, 2013 ], |
293
|
|
|
|
|
|
|
P => [ 1993, 2023 ], |
294
|
|
|
|
|
|
|
3 => [ 2003, 2033 ], |
295
|
|
|
|
|
|
|
E => [ 1984, 2014 ], |
296
|
|
|
|
|
|
|
R => [ 1994, 2024 ], |
297
|
|
|
|
|
|
|
4 => [ 2004, 2034 ], |
298
|
|
|
|
|
|
|
F => [ 1985, 2015 ], |
299
|
|
|
|
|
|
|
S => [ 1995, 2025 ], |
300
|
|
|
|
|
|
|
5 => [ 2005, 2035 ], |
301
|
|
|
|
|
|
|
G => [ 1986, 2016 ], |
302
|
|
|
|
|
|
|
T => [ 1996, 2026 ], |
303
|
|
|
|
|
|
|
6 => [ 2006, 2036 ], |
304
|
|
|
|
|
|
|
H => [ 1987, 2017 ], |
305
|
|
|
|
|
|
|
V => [ 1997, 2027 ], |
306
|
|
|
|
|
|
|
7 => [ 2007, 2037 ], |
307
|
|
|
|
|
|
|
J => [ 1988, 2018 ], |
308
|
|
|
|
|
|
|
W => [ 1998, 2028 ], |
309
|
|
|
|
|
|
|
8 => [ 2008, 2038 ], |
310
|
|
|
|
|
|
|
K => [ 1989, 2019 ], |
311
|
|
|
|
|
|
|
X => [ 1999, 2029 ], |
312
|
|
|
|
|
|
|
9 => [ 2009, 2039 ], |
313
|
|
|
|
|
|
|
); |
314
|
|
|
|
|
|
|
|
315
|
3
|
|
|
|
|
10
|
my $yearDigit = substr( $vis->{vis}, 0, 1 ); |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
my $year = |
318
|
|
|
|
|
|
|
defined $years{$yearDigit} |
319
|
3
|
100
|
|
|
|
10
|
? $years{$yearDigit} |
320
|
|
|
|
|
|
|
: undef; |
321
|
|
|
|
|
|
|
|
322
|
3
|
100
|
|
|
|
7
|
if ($year) { |
323
|
2
|
|
|
|
|
14
|
$vis->{year} = $year; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
else { |
326
|
|
|
|
|
|
|
$self->_trackError("Illegal character in 10th position: $yearDigit") |
327
|
1
|
50
|
|
|
|
6
|
unless $vis->{year}; |
328
|
|
|
|
|
|
|
} |
329
|
|
|
|
|
|
|
} |
330
|
|
|
|
|
|
|
|
331
|
3
|
|
|
|
|
8
|
return $vis; |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub _checkCheckDigit { |
335
|
3
|
|
|
3
|
|
7
|
my ( $self, $_vin ) = @_; |
336
|
|
|
|
|
|
|
|
337
|
3
|
50
|
|
|
|
9
|
return unless length($_vin) == 17; |
338
|
|
|
|
|
|
|
|
339
|
3
|
|
|
|
|
8
|
my $passedCheckDigit = substr( $_vin, 8, 1 ); |
340
|
|
|
|
|
|
|
|
341
|
3
|
|
|
|
|
24
|
my %vals = ( |
342
|
|
|
|
|
|
|
A => 1, |
343
|
|
|
|
|
|
|
B => 2, |
344
|
|
|
|
|
|
|
C => 3, |
345
|
|
|
|
|
|
|
D => 4, |
346
|
|
|
|
|
|
|
E => 5, |
347
|
|
|
|
|
|
|
F => 6, |
348
|
|
|
|
|
|
|
G => 7, |
349
|
|
|
|
|
|
|
H => 8, |
350
|
|
|
|
|
|
|
J => 1, |
351
|
|
|
|
|
|
|
K => 2, |
352
|
|
|
|
|
|
|
L => 3, |
353
|
|
|
|
|
|
|
M => 4, |
354
|
|
|
|
|
|
|
N => 5, |
355
|
|
|
|
|
|
|
P => 7, |
356
|
|
|
|
|
|
|
R => 9, |
357
|
|
|
|
|
|
|
S => 2, |
358
|
|
|
|
|
|
|
T => 3, |
359
|
|
|
|
|
|
|
U => 4, |
360
|
|
|
|
|
|
|
V => 5, |
361
|
|
|
|
|
|
|
W => 6, |
362
|
|
|
|
|
|
|
X => 7, |
363
|
|
|
|
|
|
|
Y => 8, |
364
|
|
|
|
|
|
|
Z => 9 |
365
|
|
|
|
|
|
|
); |
366
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
# Add the numeric pieces |
368
|
|
|
|
|
|
|
# these are worth face value |
369
|
3
|
|
|
|
|
11
|
for ( 0 .. 9 ) { |
370
|
30
|
|
|
|
|
54
|
$vals{$_} = $_; |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
3
|
|
|
|
|
26
|
my %wghts = ( |
374
|
|
|
|
|
|
|
1 => 8, |
375
|
|
|
|
|
|
|
2 => 7, |
376
|
|
|
|
|
|
|
3 => 6, |
377
|
|
|
|
|
|
|
4 => 5, |
378
|
|
|
|
|
|
|
5 => 4, |
379
|
|
|
|
|
|
|
6 => 3, |
380
|
|
|
|
|
|
|
7 => 2, |
381
|
|
|
|
|
|
|
8 => 10, |
382
|
|
|
|
|
|
|
9 => 0, |
383
|
|
|
|
|
|
|
10 => 9, |
384
|
|
|
|
|
|
|
11 => 8, |
385
|
|
|
|
|
|
|
12 => 7, |
386
|
|
|
|
|
|
|
13 => 6, |
387
|
|
|
|
|
|
|
14 => 5, |
388
|
|
|
|
|
|
|
15 => 4, |
389
|
|
|
|
|
|
|
16 => 3, |
390
|
|
|
|
|
|
|
17 => 2 |
391
|
|
|
|
|
|
|
); |
392
|
|
|
|
|
|
|
|
393
|
3
|
|
|
|
|
26
|
my @vinbits = split( // => $_vin ); |
394
|
|
|
|
|
|
|
|
395
|
3
|
|
|
|
|
44
|
my $sum; |
396
|
|
|
|
|
|
|
|
397
|
3
|
|
|
|
|
7
|
my $ind = 1; |
398
|
3
|
|
|
|
|
7
|
for my $bit (@vinbits) { |
399
|
51
|
|
|
|
|
83
|
$sum += $vals{$bit} * $wghts{$ind}; |
400
|
51
|
|
|
|
|
63
|
$ind++; |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
3
|
|
|
|
|
7
|
my $calcCheckDigit = $sum % 11; |
404
|
3
|
50
|
|
|
|
10
|
$calcCheckDigit = 'X' |
405
|
|
|
|
|
|
|
if $calcCheckDigit == '10'; |
406
|
|
|
|
|
|
|
|
407
|
3
|
100
|
|
|
|
41
|
$calcCheckDigit =~ /$passedCheckDigit/ |
408
|
|
|
|
|
|
|
? return $calcCheckDigit |
409
|
|
|
|
|
|
|
: $self->_trackError( |
410
|
|
|
|
|
|
|
"Checkdigit mismatch; expected $calcCheckDigit, got $passedCheckDigit"); |
411
|
|
|
|
|
|
|
|
412
|
2
|
|
|
|
|
14
|
return; |
413
|
|
|
|
|
|
|
} |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
sub _loadCountry { |
416
|
258
|
|
|
258
|
|
569
|
my ( $self, $start, $end, $country, $store ) = @_; |
417
|
|
|
|
|
|
|
|
418
|
258
|
|
|
|
|
455
|
$store->{$start} = $country; |
419
|
|
|
|
|
|
|
|
420
|
258
|
|
|
|
|
456
|
until ( $start eq $end ) { |
421
|
1752
|
|
|
|
|
3290
|
my @pieces = split( // => $start ); |
422
|
1752
|
|
|
|
|
3214
|
my $next = $self->_next( $pieces[1] ); |
423
|
|
|
|
|
|
|
|
424
|
1752
|
|
|
|
|
2661
|
$start = $pieces[0] . $next; |
425
|
|
|
|
|
|
|
|
426
|
1752
|
|
|
|
|
4650
|
$store->{$start} = $country; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
|
429
|
258
|
|
|
|
|
415
|
return; |
430
|
|
|
|
|
|
|
} |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
sub _next { |
433
|
1752
|
|
|
1752
|
|
2847
|
my ( $self, $current ) = @_; |
434
|
|
|
|
|
|
|
|
435
|
1752
|
|
|
|
|
6467
|
my @fields = qw{ A B C D E F G H J K L M N P R S T |
436
|
|
|
|
|
|
|
U V W X Y Z 1 2 3 4 5 6 7 8 9 0 }; |
437
|
|
|
|
|
|
|
|
438
|
1752
|
|
|
|
|
3445
|
my %order = map { ( $fields[$_], $_ ) } 0 .. scalar(@fields) - 1; |
|
57816
|
|
|
|
|
86459
|
|
439
|
|
|
|
|
|
|
|
440
|
1752
|
|
|
|
|
5503
|
my $max = scalar(@fields) - 1; |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
my $next = |
443
|
|
|
|
|
|
|
$order{$current} == $max |
444
|
|
|
|
|
|
|
? $fields[0] |
445
|
1752
|
50
|
|
|
|
4020
|
: $fields[ $order{$current} + 1 ]; |
446
|
|
|
|
|
|
|
|
447
|
1752
|
|
|
|
|
8053
|
return $next; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
sub _trackError { |
451
|
17
|
|
|
17
|
|
32
|
my ( $self, $error ) = @_; |
452
|
|
|
|
|
|
|
|
453
|
17
|
|
|
|
|
27
|
push @{ $self->{errors} }, $error; |
|
17
|
|
|
|
|
35
|
|
454
|
|
|
|
|
|
|
|
455
|
17
|
|
|
|
|
39
|
return; |
456
|
|
|
|
|
|
|
} |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
1; |
459
|
|
|
|
|
|
|
__END__ |