however, as the project grew, I was informed that they only wanted to use the last 9 digits (instead of the full 17); so validation became impossible, and pointless.
In case anyone else needs it; here is my "wwip" (was work in progress) of vin validation. Hope someone else can use it.
Code: Select all
EnableExplicit
;- Constants & Enumerations
Enumeration vinResults
#vin_Valid
#vin_RegExErr
#vin_FailChkDigit
#vin_Invalid
#vin_InvalidLength
#vin_InvalidCharacter
EndEnumeration
;{ Notes on VIN
; VIN contains,encoded, these parts: WMI, Maker, Model, Axles, Type, GVWR
; 1-3 WMI
; 4-8 VDS Vehicle Attributes (body/etc)
; 9 Check Digit
; 10 Year
; 11 Plant Code
; 12-17 sequential number
Enumeration vinWMIParts
#vinpart_WMI
#vinpart_Country
#vinpart_Maker
#vinpart_Model
#vinpart_Axles
#vinpart_Type
#vinpart_GVWR
EndEnumeration
;}
; RegEx for vin match/extract
#vinRegExMatch$ = "^(([A-H,J-N,P-Z,0-9]{9})([A-H,J-N,P,R-T,V-Z,0-9])([A-H,J-N,P-Z,0-9]{7}))$"
#vinRegExExtract$ = "(([A-H,J-N,P-Z,0-9]{9})([A-H,J-N,P,R-T,V-Z,0-9])([A-H,J-N,P-Z,0-9]{7}))"
; Could use a more presice w/ lookahead, but it's not needed (?=.{17,})(([A-H,J-N,P-Z,0-9]{9})([A-H,J-N,P,R-T,V-Z,0-9])([A-H,J-N,P-Z,0-9]{7}))
; If you want to exract more "invalid" to look for invalid entries (characters/lengths/etc) you'd need to craft your own extract routine
Macro isdigit(c)
(FindString("0123456789",c) <> -1)
EndMacro
Procedure VINValidation(cVehicleIdentNumber.s)
Protected.i nResult, nCheckDigit, i, nSum
Protected.s cChar
Static Dim Weights(16) ; 17 elements
Static NewMap Transliterations() ; Use of static vars to help speed up subsequent calls. Doesn't help too much; but it does help some.
Static hRegEx ; help a little you are validating a million,etc (Static vs creating/releasing it each call)
If Len(cVehicleIdentNumber) = 17
If IsRegularExpression(hRegEx)=#False
hRegEx = CreateRegularExpression(#PB_Any, #vinRegExMatch$, #PB_RegularExpression_NoCase)
EndIf
If IsRegularExpression(hRegEx)
If MatchRegularExpression(hRegEx,cVehicleIdentNumber)
If MapSize( Transliterations() ) = 0 ;{ Load Weights & Transliteration tables
Restore vinWeights
For i = 0 To 16
Read.i weights(i)
Next
Restore vinTransliterations
For i = 1 To 23
Read.s cChar
Transliterations(cChar)=0
Next
ForEach Transliterations()
Read.i Transliterations()
Next
EndIf ;} End loading weights & transliterations tables
; Start processing...
cVehicleIdentNumber = LCase(cVehicleIdentNumber);
For i = 0 To 16
cChar = Mid(cVehicleIdentNumber, i+1, 1)
; add transliterations * weight of their positions To get the sum
If isdigit( cChar)
nSum + ( Val(cChar) * weights(i) );
Else
nSum + ( transliterations(cChar) * weights(i) )
EndIf
Next
nCheckDigit = nSum % 11 ; find checkdigit by taking the mod of the sum
If(nCheckDigit = 10)
nCheckDigit = 0
EndIf
If nCheckDigit = Val(Mid(cVehicleIdentNumber, 9, 1))
nResult = #vin_Valid
Else
nResult = #vin_FailChkDigit
EndIf
Else
nResult = #vin_Invalid
EndIf ; match regex
;FreeRegularExpression(hRegEx)
Else
nResult = #vin_RegExErr
EndIf
Else
nResult = #vin_InvalidLength
EndIf
ProcedureReturn nResult
EndProcedure
; Named 'potential' because it still needs to be validated ...
Procedure.s ExtractPotentialVIN( cTextSample.s, nIndex.i=0)
Protected.s cPotentialVIN
Protected Dim aResults.s(1)
Static hRegEx
If IsRegularExpression(hRegEx)=#False
hRegEx = CreateRegularExpression(#PB_Any,#vinRegExExtract$,#PB_RegularExpression_NoCase)
EndIf
If IsRegularExpression(hRegEx)
If ExtractRegularExpression(hRegEx, cTextSample, aResults()) > nIndex
cPotentialVIN = aResults(nIndex)
EndIf
;FreeArray( aResults() )
Else
Debug "Failed to create regex for extract"
EndIf
;FreeRegularExpression(hRegEx)
ProcedureReturn cPotentialVIN
EndProcedure
DataSection ;{ Note: using data&read vs assigning each array/map individually was marginally slower, so I left it as data as in reality it should only be loaded once per program run.
vinWeights:
Data.i 8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2
vinTransliterations:
Data.s "a","b","c","d","e","f","g","h","j","k","l","m","n","p","r","s","t","u","v","w","x","y","z"
Data.i 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 1 , 2 , 3 , 4 , 5 , 7 , 9 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9
EndDataSection ;}
CompilerIf #False ; Data not used, but left here in case it helps someone else
; Personally.... I would load this into an sql database if you plan on using this on a regular basis
DataSection ; Read group at at time (2 or 3 items) and blank = end of data
vinWMI:;{ code,country,company
Data.s "1C", "United States", "Chrysler", "1F", "United States", "Ford", "1G", "United States", "General Motors", "1G3", "United States", "Oldsmobile", "1GC", "United States", "Chevrolet", "1GM", "United States", "Pontiac", "1H", "United States", "Honda", "1J", "United States", "Jeep", "1L", "United States", "Lincoln", "1M", "United States", "Mercury"
Data.s "1N", "United States", "Nissan", "1VW", "United States", "Volkswagen", "1YV", "United States", "Mazda", "2F", "Canada", "Ford", "2G", "Canada", "General Motors", "2G1", "Canada", "Chevrolet", "2G2", "Canada", "Pontiac", "2H", "Canada", "Honda", "2HM", "Canada", "Hyundai", "2M", "Canada", "Mercury"
Data.s "2T", "Canada", "Toyota", "3F", "Mexico", "Ford", "3G", "Mexico", "General Motors", "3H", "Mexico", "Honda", "3N", "Mexico", "Nissan", "3VW", "Mexico", "Volkswagen", "4F", "United States", "Mazda", "4J", "United States", "Mercedes-Benz", "4M", "United States", "Mercury", "4S", "United States", "Subaru"
Data.s "4T", "United States", "Toyota", "4US", "United States", "BMW", "5F", "United States", "Honda", "5L", "United States", "Lincoln", "5T", "United States", "Toyota", "5YJ", "United States", "Tesla", "6F", "Australia", "Ford", "6G", "Australia", "General Motors", "6G1", "Australia", "Chevrolet", "6G2", "Australia", "Pontiac"
Data.s "6H", "Australia", "Holden", "6MM", "Australia", "Mitsubishi", "6T1", "Australia", "Toyota", "8AP", "Argentina", "Fiat", "8AT", "Argentina", "Iveco", "93H", "Brazil", "Honda", "93W", "Brazil", "Fiat Professional", "93Z", "Brazil", "Iveco", "9BD", "Brazil", "Fiat Autom�veis", "9BH", "Brazil", "Hyundai"
Data.s "9BW", "Brazil", "Volkswagen", "AAV", "South Africa", "Volkswagen", "AFA", "South Africa", "Ford", "CL9", "Tunisia", "Wallyscar", "JA", "Japan", "Isuzu", "JF", "Japan", "Fuji Heavy Industries", "JH", "Japan", "Honda", "JMB", "Japan", "Mitsubishi", "JMZ", "Japan", "Mazda", "JN", "Japan", "Nissan"
Data.s "JS", "Japan", "Suzuki", "JT", "Japan", "Toyota", "KL", "South Korea", "Daewoo", "KMH", "South Korea", "Hyundai", "KN", "South Korea", "Kia", "KPT", "South Korea", "SsangYong", "SAJ", "United Kingdom", "Jaguar", "SAL", "United Kingdom", "Land Rover", "SAR", "United Kingdom", "Rover", "SCC", "United Kingdom", "Lotus Cars"
Data.s "SCE", "Ireland", "DeLorean", "TCC", "Switzerland", "Micro Compact Car AG", "TMA", "Czech Republic", "Hyundai", "TMB", "Czech Republic", "�koda", "TRU", "Hungary", "Audi", "UU", "Romania", "Dacia", "VA0", "Austria", "�AF", "VF1", "France", "Renault", "VF3", "France", "Peugeot", "VF6", "France", "Renault Trucks/Volvo"
Data.s "VF7", "France", "Citro�n", "VFE", "France", "IvecoBus", "VSS", "Spain", "SEAT", "VV9", "Spain", "Tauro Sport Auto", "W0L", "Germany", "Opel/Vauxhall", "W0SV", "Germany", "Opel Special Vehicles", "WAP", "Germany", "Alpina", "WAU", "Germany", "Audi", "WBA", "Germany", "BMW", "WBS", "Germany", "BMW M"
Data.s "WDB", "Germany", "Mercedes-Benz", "WDC", "Germany", "DaimlerChrysler AG/Daimler AG", "WDD", "Germany", "DaimlerChrysler AG/Daimler AG", "WMX", "Germany", "DaimlerChrysler AG/Daimler AG", "WEB", "Germany", "EvoBus", "WF0", "Germany", "Ford Germany", "WJM", "Germany", "Iveco", "WJR", "Germany", "Irmscher", "WKK", "Germany", "K�ssbohrer", "WMA", "Germany", "MAN"
Data.s "WME", "Germany", "Smart", "WMW", "Germany", "Mini", "WP0", "Germany", "Porsche car", "WP1", "Germany", "Porsche SUV", "WUA", "Germany", "Quattro", "WV1", "Germany", "Volkswagen Commercial Vehicles", "WV2", "Germany", "Volkswagen Commercial Vehicles", "WVG", "Germany", "Volkswagen", "WVW", "Germany", "Volkswagen", "YK1", "Finland", "Saab"
Data.s "YS3", "Sweden", "Saab", "YTN", "Sweden", "Saab NEVS", "YV1", "Sweden", "Volvo Cars", "ZAM", "Italy", "Maserati", "ZAR", "Italy", "Alfa Romeo", "ZCF", "Italy", "Iveco", "ZFA", "Italy", "Fiat Automobiles", "ZFF", "Italy", "Ferrari", "ZGA", "Italy", "IvecoBus", "ZHW", "Italy", "Lamborghini"
Data.s "ZLA", "Italy", "Lancia"
Data.s "","","" ;}
vinCountryData: ;{ start-code, end-code, country
; A–H = Africa J–R = Asia S–Z = Europe 1–5 = North America 6–7 = Oceania 8–9 = South America
Data.s "AA", "AH", "South Africa", "AJ", "AN", "Ivory Coast", "AP", "A0", "not assigned", "BA", "BE", "Angola", "BF", "BK", "Kenya", "BL", "BR", "Tanzania", "BS", "B0", "not assigned", "CA", "CE", "Benin", "CF", "CK", "Madagascar", "CL", "CR", "Tunisia", "CS", "C0", "not assigned", "DA", "DE", "Egypt", "DF", "DK", "Morocco", "DL", "DR", "Zambia", "DS", "D0", "not assigned", "EA", "EE", "Ethiopia", "EF", "EK", "Mozambique", "EL", "E0", "not assigned", "FA", "FE", "Ghana", "FF", "FK", "Nigeria", "FL", "F0", "not assigned", "GA", "G0", "not assigned", "HA", "H0", "not assigned"
Data.s "JA", "J0", "Japan", "KA", "KE", "Sri Lanka", "KF", "KK", "Israel", "KL", "KR", "Korea (South)", "KS", "K0", "Kazakhstan", "LA", "L0", "China", "MA", "ME", "India", "MF", "MK", "Indonesia", "ML", "MR", "Thailand", "MS", "M0", "not assigned", "NA", "NE", "Iran", "NF", "NK", "Pakistan", "NL", "NR", "Turkey", "NS", "N0", "not assigned", "PA", "PE", "Philippines", "PF", "PK", "Singapore", "PL", "PR", "Malaysia", "PS", "P0", "not assigned", "RA", "RE", "United Arab Emirates", "RF", "RK", "Taiwan", "RL", "RR", "Vietnam", "RS", "R0", "Saudi Arabia"
Data.s "SA", "SM", "United Kingdom", "SN", "ST", "East Germany", "SU", "SZ", "Poland", "S1", "S4", "Latvia", "S5", "S0", "not assigned", "TA", "TH", "Switzerland", "TJ", "TP", "Czech Republic", "TR", "TV", "Hungary", "TW", "T1", "Portugal", "T2", "T0", "not assigned", "UA", "UG", "not assigned", "UH", "UM", "Denmark", "UN", "UT", "Ireland", "UU", "UZ", "Romania", "U1", "U4", "not assigned", "U5", "U7", "Slovakia", "U8", "U0", "not assigned", "VA", "VE", "Austria", "VF", "VR", "France", "VS", "VW", "Spain", "VX", "V2", "Serbia", "V3", "V5", "Croatia", "V6", "V0", "Estonia", "WA", "W0", "West Germany", "XA", "XE", "Bulgaria", "XF", "XK", "Greece", "XL", "XR", "Netherlands", "XS", "XW", "USSR", "XX", "X2", "Luxembourg", "X3", "X0", "Russia", "YA", "YE", "Belgium", "YF", "YK", "Finland", "YL", "YR", "Malta", "YS", "YW", "Sweden", "YX", "Y2", "Norway", "Y3", "Y5", "Belarus", "Y6", "Y0", "Ukraine", "ZA", "ZR", "Italy", "ZS", "ZW", "not assigned", "ZX", "Z2", "Slovenia", "Z3", "Z5", "Lithuania", "Z6", "Z0", "not assigned"
Data.s "1A", "10", "United States", "2A", "20", "Canada", "3A", "37", "Mexico", "38", "30", "Cayman Islands", "4A", "40", "United States", "5A", "50", "United States"
Data.s "6A", "6W", "Australia", "6X", "60", "not assigned", "7A", "7E", "New Zealand", "7F", "70", "not assigned"
Data.s "8A", "8E", "Argentina", "8F", "8K", "Chile", "8L", "8R", "Ecuador", "8S", "8W", "Peru", "8X", "82", "Venezuela", "83", "80", "not assigned", "9A", "9E", "Brazil", "9F", "9K", "Colombia", "9L", "9R", "Paraguay", "9S", "9W", "Uruguay", "9X", "92", "Trinidad & Tobago", "93�99", "", "Brazil", "90 no", "", " assigned"
Data.s "","","" ;}
vinYearData: ;{ code, year
Data.s "A","1980", "L","1990", "Y","2000", "A","2010", "L","2020", "Y","2030"
Data.s "B","1981", "M","1991", "1","2001", "B","2011", "M","2021", "1","2031"
Data.s "C","1982", "N","1992", "2","2002", "C","2012", "N","2022", "2","2032"
Data.s "D","1983", "P","1993", "3","2003", "D","2013", "P","2023", "3","2033"
Data.s "E","1984", "R","1994", "4","2004", "E","2014", "R","2024", "4","2034"
Data.s "F","1985", "S","1995", "5","2005", "F","2015", "S","2025", "5","2035"
Data.s "G","1986", "T","1996", "6","2006", "G","2016", "T","2026", "6","2036"
Data.s "H","1987", "V","1997", "7","2007", "H","2017", "V","2027", "7","2037"
Data.s "J","1988", "W","1998", "8","2008", "J","2018", "W","2028", "8","2038"
Data.s "K","1989", "X","1999", "9","2009", "K","2019", "X","2029", "9","2039"
Data.s "","" ;}
vinVechicleConfigData: ;{ code, description
Data.s "1", "Passenger Car (only If vehicle has HM Placard)"
Data.s "2", "Light Truck (only If vehicle has HM Placard)"
Data.s "3", "Bus (seats For 9-15 people, including driver)"
Data.s "4", "Bus (seats For 16 people Or more, including driver)"
Data.s "5", "Single-Unit Truck (2 axles, 6 tires)"
Data.s "6", "Single-Unit Truck (3 Or more axles)"
Data.s "7", "Truck/Trailer(s) (Single-Unit Truck With Trailer(s))"
Data.s "8", "Truck/Tractor (without trailer, Bobtail Or saddlemount)"
Data.s "9", "Tractor/Semi-Trailer (one trailer)"
Data.s "10", "Tractor/Doubles (two trailers)"
Data.s "11", "Tractor/Triples (three trailers)"
Data.s "99", "Other Heavy Truck >10,000 lbs. Unclassified (Not listed above)"
Data.s "","" ;}
vinVechicleGVWR: ;{ code, weight
Data.s, "1", "10,000 lbs. Or less"
Data.s, "2", " 10,001 lbs. To 26,000 lbs."
Data.s, "3", "Greater than 26,000 lbs."
Data.s "","" ;}
vinVehicleCargoBodyType: ;{ code, type
Data.s "1", "Bus (seats For 9-15 people, including driver)"
Data.s "2", "Bus (seats For 16 people Or more, including driver)"
Data.s "3", "Van/Enclosed Box"
Data.s "4", "Cargo Tank"
Data.s "5", "Flatbed"
Data.s "6", "Dump"
Data.s "7", "Concrete Mixer"
Data.s "8", "Auto Transporter"
Data.s "9", "Garbage/Refuse"
Data.s "10", "Grain, Chips, Gravel"
Data.s "11", "Pole"
Data.s "12", "Not Applicable/No Cargo Body Type"
Data.s "13", "Intermodal Chassis"
Data.s "14", "Logging"
Data.s "15", "Vehicle Towing Another Motor Vehicle"
Data.s "98", "Other"
Data.s "","" ;}
EndDataSection
CompilerEndIf
CompilerIf #PB_Compiler_IsMainFile
Debug Str(#vin_Valid)+" = success"
Debug Str(#vin_FailChkDigit)+" = fail chkdigit"
Debug Str(#vin_InvalidCharacter)+" = invalid char"
Debug "Test: "+VINValidation("1M8GDM9AXKP042788") ; Valid
Debug "Test: "+VINValidation("1FTYR10D49PA42063") ; Valid
Debug "Test: "+VINValidation("1MZGDM9AXKP042788") ; Invalid
Debug "Test: "+VINValidation("1MZG*M9AXKP042788") ; invalid character
Define.s cText = "1M8GDM9AXKP042788"+#CRLF$+"1FTYR10D49PA42063"+"1MZGDM9AXKP042788"+#TAB$+"1ZZGDM9AXKP042788"+"1MZG*M9AXKP042788", cSample
; note, the last will not be picked up since it fails the regex for extraction.
Debug "Showing potential vins extracted"
Define.i i, nResultCode
While ExtractPotentialVIN(cText, i)
cSample = ExtractPotentialVIN(cText,i)
nResultCode = VINValidation( cSample )
Select nResultCode
Case #vin_Valid : Debug Str(i)+" VALID "+ cSample
Case #vin_RegExErr : Debug Str(i)+" RegEx error "+cSample
Case #vin_FailChkDigit : Debug Str(i)+" Failed check digit "+cSample
Case #vin_Invalid : Debug Str(i)+" Invalid "+cSample
Case #vin_InvalidLength : Debug Str(i)+" Too Short/long "+cSample
Case #vin_InvalidCharacter : Debug Str(i)+" InvalidChar found "+cSample
Default : Debug Str(i)+" Unknown Result code: "+nResultCode
EndSelect
i+1
Wend
CompilerEndIf