Skip to content

Instantly share code, notes, and snippets.

@mrkishi
Last active November 5, 2016 00:30
Show Gist options
  • Save mrkishi/5010bcf8fc09252334d9dcfb230d7e6c to your computer and use it in GitHub Desktop.
Save mrkishi/5010bcf8fc09252334d9dcfb230d7e6c to your computer and use it in GitHub Desktop.
AAMVA Magnetic Stripe Card Parser
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>AAMVA Magnetic Stripe Card Parser</title>
<style>
html {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
output {
display: block;
white-space: pre;
}
</style>
</head>
<body>
<textarea cols="84" rows="3" autofocus></textarea>
<output></output>
<script>
const input = document.querySelector('textarea')
const output = document.querySelector('output')
const parse = (() => {
const start_sentinel1 = `\\%`
const field_separator1 = `\\^`
const field1 = `[^${field_separator1}]`
const start_sentinel2 = `\\;`
const field_separator2 = `\\=`
const field2 = `[^${field_separator2}]`
const start_sentinel3 = `[\\#\\%\\+]`
const lrc = `.?`
const end_sentinel = `\\?`
const regex = new RegExp(''
// track 1
+ start_sentinel1
+ `(.{2})` // 1: state
+ `(${field1}{0,13})${field_separator1}?` // 2: city
+ `(${field1}{0,35})${field_separator1}?` // 3: name
+ `(${field1}*)${field_separator1}?` // 4: address
+ end_sentinel
+ lrc
// track 2
+ start_sentinel2
+ `(?:(?:`
+ `(${field2}{6})` // 5: IIN
+ `(${field2}{0,13})${field_separator2}` // 6: DL/ID
+ `(..)(..)` // 7, 8: expiration date (y, m)
+ `..(..)(..)(..)` // 9, 10, 11: birth date (y, m, d)
+ `(${field2}{0,5})${field_separator2}?` // 12: DL/ID overflow
+ `)|(?:.))`
+ end_sentinel
+ lrc
// track 3
+ start_sentinel3
+ `(.)` // 13: template version
+ `(.)` // 14: security version
+ `(.{11})` // 15: postal code
+ `(.{2})` // 16: class
+ `(.{10})` // 17: restrictions
+ `(.{4})` // 18: endorsements
+ `(.{1})` // 19: sex
+ `(.{3})` // 20: height
+ `(.{3})` // 21: weight
+ `(.{3})` // 22: hair color
+ `(.{3})` // 23: eyes color
/*
+ `(.{10})?` // 24: id
+ `(.{16})?` // 25: reserved
+ `(.{6})?` // 26: error correction
+ `(.{5})?` // 27: security
+ end_sentinel
+ lrc
*/
)
const delimiter = `$`
const delimiters = /\$/g
const sex = {
'1': 'MALE',
'M': 'MALE',
'2': 'FEMALE',
'F': 'FEMALE',
}
return (text) => {
const data = text.value.replace(/[\r\n]/g, '')
const m = data.match(regex)
if (!m) {
return null
}
return {
state: m[1],
city: m[2],
name: m[3].replace(delimiter, ', ').replace(delimiter, ' ').trim(),
address: m[4].replace(delimiters, '\n').trim(),
iin: m[5],
dl_id: m[6] ? (() => {
if (m[1] === 'FL') {
return String.fromCharCode(64 + parseInt(m[6].slice(0, 2), 10))
+ m[6].slice(2) + m[12]
} else {
return m[6] + m[12]
}
})() : undefined,
expiration_date: m[8] ? (() => {
switch (m[8]) {
case '77':
return 'NON-EXPIRING'
case '88':
return `${m[10]}/${m[7]}`
case '99':
return `${m[10]}/${m[11]}/m[7]`
default:
return `${m[8]}/${m[7]}`
}
})() : undefined,
birth_date: m[10] ? (() => {
if (m[10] === '99') {
m[10] = m[8]
}
return `${m[10]}/${m[11]}/${m[9]}`
})() : undefined,
template_version: m[13],
security_version: m[14],
zip_code: m[15].trim(),
class: m[16].trim(),
restrictions: m[17].trim(),
endorsements: m[18].trim(),
sex: sex[m[19].toUpperCase()] || 'UNKNOWN/MISSING',
height: m[20].trim(),
weight: m[21].trim(),
hair_color: m[22].trim(),
eyes_color: m[23].trim(),
/*
id_number: (m[24] || '').trim(),
reserved: (m[25] || '').trim(),
error_correction: (m[26] || '').trim(),
security: (m[27] || '').trim(),
*/
}
}
})()
input.addEventListener('input', (event) => {
const data = parse(input)
if (!data) {
output.textContent = 'Incompatible format'
} else {
output.textContent = `
${data.name}
${data.address.replace('\n', '\n\t')}
${data.city}, ${data.state}
DL#: ${data.dl_id || '-'}
Expiration: ${data.expiration_date || '-'}
DOB: ${data.birth_date || '-'}
IIN: ${data.iin || '-'}
ZIP: ${data.zip_code || '-'}
Class: ${data.class || '-'}
Restrictions: ${data.restrictions || '-'}
Endorsements: ${data.endorsements || '-'}
Sex: ${data.sex}
Height: ${data.height || '-'}
Weight: ${data.weight || '-'}
Hair color: ${data.hair_color || '-'}
Eyes color: ${data.eyes_color || '-'}
`
}
})
document.addEventListener('keydown', (event) => {
if (!event.ctrlKey && !event.metaKey) {
input.focus()
}
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment