/*
|
* Copyright 2014 ZXing authors
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#import "ZXVINParsedResult.h"
|
#import "ZXVINResultParser.h"
|
|
static NSRegularExpression *ZX_IOQ = nil;
|
static NSRegularExpression *ZX_AZ09 = nil;
|
|
@implementation ZXVINResultParser
|
|
+ (void)initialize {
|
if ([self class] != [ZXVINResultParser class]) return;
|
|
ZX_IOQ = [[NSRegularExpression alloc] initWithPattern:@"[IOQ]" options:0 error:nil];
|
ZX_AZ09 = [[NSRegularExpression alloc] initWithPattern:@"[A-Z0-9]{17}" options:0 error:nil];
|
}
|
|
- (ZXVINParsedResult *)parse:(ZXResult *)result {
|
if (result.barcodeFormat != kBarcodeFormatCode39) {
|
return nil;
|
}
|
NSString *rawText = result.text;
|
rawText = [[ZX_IOQ stringByReplacingMatchesInString:rawText options:0 range:NSMakeRange(0, rawText.length) withTemplate:@""] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
if ([ZX_AZ09 matchesInString:rawText options:0 range:NSMakeRange(0, rawText.length)] == 0) {
|
return nil;
|
}
|
if (![self checkChecksum:rawText]) {
|
return nil;
|
}
|
|
int modelYear = [self modelYear:[rawText characterAtIndex:9]];
|
if (modelYear == -1) {
|
return nil;
|
}
|
|
NSString *wmi = [rawText substringToIndex:3];
|
return [[ZXVINParsedResult alloc] initWithVIN:rawText
|
worldManufacturerID:wmi
|
vehicleDescriptorSection:[rawText substringWithRange:NSMakeRange(3, 6)]
|
vehicleIdentifierSection:[rawText substringWithRange:NSMakeRange(9, 8)]
|
countryCode:[self countryCode:wmi]
|
vehicleAttributes:[rawText substringWithRange:NSMakeRange(3, 5)]
|
modelYear:modelYear
|
plantCode:[rawText characterAtIndex:10]
|
sequentialNumber:[rawText substringFromIndex:11]];
|
}
|
|
- (BOOL)checkChecksum:(NSString *)vin {
|
int sum = 0;
|
for (int i = 0; i < [vin length]; i++) {
|
int vinPositionWeight = [self vinPositionWeight:i + 1];
|
if (vinPositionWeight == -1) {
|
return NO;
|
}
|
int vinCharValue = [self vinCharValue:[vin characterAtIndex:i]];
|
if (vinCharValue == -1) {
|
return NO;
|
}
|
sum += vinPositionWeight * vinCharValue;
|
}
|
unichar checkChar = [vin characterAtIndex:8];
|
if (checkChar == '\0') {
|
return NO;
|
}
|
unichar expectedCheckChar = [self checkChar:sum % 11];
|
return checkChar == expectedCheckChar;
|
}
|
|
- (int)vinCharValue:(unichar)c {
|
if (c >= 'A' && c <= 'I') {
|
return (c - 'A') + 1;
|
}
|
if (c >= 'J' && c <= 'R') {
|
return (c - 'J') + 1;
|
}
|
if (c >= 'S' && c <= 'Z') {
|
return (c - 'S') + 2;
|
}
|
if (c >= '0' && c <= '9') {
|
return c - '0';
|
}
|
return -1;
|
}
|
|
- (int)vinPositionWeight:(int)position {
|
if (position >= 1 && position <= 7) {
|
return 9 - position;
|
}
|
if (position == 8) {
|
return 10;
|
}
|
if (position == 9) {
|
return 0;
|
}
|
if (position >= 10 && position <= 17) {
|
return 19 - position;
|
}
|
return -1;
|
}
|
|
- (unichar)checkChar:(int)remainder {
|
if (remainder < 10) {
|
return (unichar) ('0' + remainder);
|
}
|
if (remainder == 10) {
|
return 'X';
|
}
|
return '\0';
|
}
|
|
- (int)modelYear:(unichar)c {
|
if (c >= 'E' && c <= 'H') {
|
return (c - 'E') + 1984;
|
}
|
if (c >= 'J' && c <= 'N') {
|
return (c - 'J') + 1988;
|
}
|
if (c == 'P') {
|
return 1993;
|
}
|
if (c >= 'R' && c <= 'T') {
|
return (c - 'R') + 1994;
|
}
|
if (c >= 'V' && c <= 'Y') {
|
return (c - 'V') + 1997;
|
}
|
if (c >= '1' && c <= '9') {
|
return (c - '1') + 2001;
|
}
|
if (c >= 'A' && c <= 'D') {
|
return (c - 'A') + 2010;
|
}
|
return -1;
|
}
|
|
- (NSString *)countryCode:(NSString *)wmi {
|
unichar c1 = [wmi characterAtIndex:0];
|
unichar c2 = [wmi characterAtIndex:1];
|
switch (c1) {
|
case '1':
|
case '4':
|
case '5':
|
return @"US";
|
case '2':
|
return @"CA";
|
case '3':
|
if (c2 >= 'A' && c2 <= 'W') {
|
return @"MX";
|
}
|
break;
|
case '9':
|
if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) {
|
return @"BR";
|
}
|
break;
|
case 'J':
|
if (c2 >= 'A' && c2 <= 'T') {
|
return @"JP";
|
}
|
break;
|
case 'K':
|
if (c2 >= 'L' && c2 <= 'R') {
|
return @"KO";
|
}
|
break;
|
case 'L':
|
return @"CN";
|
case 'M':
|
if (c2 >= 'A' && c2 <= 'E') {
|
return @"IN";
|
}
|
break;
|
case 'S':
|
if (c2 >= 'A' && c2 <= 'M') {
|
return @"UK";
|
}
|
if (c2 >= 'N' && c2 <= 'T') {
|
return @"DE";
|
}
|
break;
|
case 'V':
|
if (c2 >= 'F' && c2 <= 'R') {
|
return @"FR";
|
}
|
if (c2 >= 'S' && c2 <= 'W') {
|
return @"ES";
|
}
|
break;
|
case 'W':
|
return @"DE";
|
case 'X':
|
if (c2 == '0' || (c2 >= '3' && c2 <= '9')) {
|
return @"RU";
|
}
|
break;
|
case 'Z':
|
if (c2 >= 'A' && c2 <= 'R') {
|
return @"IT";
|
}
|
break;
|
}
|
return nil;
|
}
|
|
@end
|