Skip to content

Commit

Permalink
Add support for X gender marker, for real
Browse files Browse the repository at this point in the history
  • Loading branch information
meanderfox committed Mar 2, 2022
1 parent c361f7c commit 471032e
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 47 deletions.
72 changes: 43 additions & 29 deletions aamva/aamva.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,11 @@
IMPERIAL = "USA"
MALE = "M"
FEMALE = "F"
UNSPECIFIED = "U"
NOT_SPECIFIED = "X"
DRIVER_LICENSE = "DL"
IDENTITY_CARD = "ID"
EYECOLOURS = ["BLK", "BLU", "BRO", "GRY", "HAZ", "MAR", "PNK", "DIC", "UNK", "GRN"]
EYECOLOURS = ["BLK", "BLU", "BRO", "GRY",
"HAZ", "MAR", "PNK", "DIC", "UNK", "GRN"]
HAIRCOLOURS = ["BAL", "BLK", "BLN", "BRO", "GRY", "RED", "SDY", "WHI", "UNK"]
# Human-readable weight ranges
METRIC_WEIGHTS = {
Expand Down Expand Up @@ -185,9 +186,9 @@ def decode(self, data=None):
Note that the issue date is missing from the magstripe encoding, and
will always be represented by 'None'.
"""
if data == None:
if data is None:
data = self.data
if data == None:
if data is None:
raise ValueError("No data to parse")

for form in self.format:
Expand Down Expand Up @@ -221,7 +222,8 @@ def decode_magstripe(self, data):
# city+name+address together should be no longer than 77 chars.
name_pre = fields[0][16:]
address = fields[1].split("$")
remaining = fields[2] # track 2 data will be one behind in this case
# track 2 data will be one behind in this case
remaining = fields[2]
else:
name_pre = fields[1]
address = fields[2].split("$")[0]
Expand Down Expand Up @@ -258,15 +260,16 @@ def decode_magstripe(self, data):
) - datetime.timedelta(days=1)

dob_str = track2[1][4:12] # e.g. 19850215
dob = datetime.date(int(dob_str[0:4]), int(dob_str[4:6]), int(dob_str[6:8]))
dob = datetime.date(int(dob_str[0:4]), int(
dob_str[4:6]), int(dob_str[6:8]))

# parse track3:
template = track3[
1:2
] # FIXME: according to A.4.3 should only be 0,2 but mine says 1
1:2
] # FIXME: according to A.4.3 should only be 0,2 but mine says 1
security = track3[
2:3
] # FIXME: docs says 1 character long but says 00-63 are valid values.
2:3
] # FIXME: docs says 1 character long but says 00-63 are valid values.
postal_code = track3[3:14].strip() # remove space padding
license_class = track3[14:16].strip()
restrictions = track3[16:26].strip()
Expand All @@ -289,7 +292,7 @@ def decode_magstripe(self, data):
if weight != "":
weight = Weight(None, int(weight), "USA")
else:
weight == None
weight is None
# cast height (Also assumes no one is taller than 9'11"
height = Height((int(height[0]) * 12) + int(height[1:]), "USA")

Expand Down Expand Up @@ -334,17 +337,17 @@ def decode_barcode(self, data):
# segterm = '\x0a' # Have to override to work with SC old format
else:
assert (
data[2] == PDF_RECORDSEP
data[2] == PDF_RECORDSEP
), "Missing record separator (RS) got (%s)" % repr(data[2])
assert data[3] == PDF_SEGTERM, "Missing segment terminator (CR)"
assert (data[4:9] == PDF_FILETYPE) or (data[4:9] == "AAMVA"), (
'Wrong file type (got "%s", should be "ANSI ")' % data[4:9]
'Wrong file type (got "%s", should be "ANSI ")' % data[4:9]
)
issue_identifier = data[9:15]
assert issue_identifier.isdigit(), "Issue Identifier is not an integer"
version = int(data[15:17])
assert version in PDF_VERSIONS, (
"Invalid data version number (got %s, should be 0 - 63)" % version
"Invalid data version number (got %s, should be 0 - 63)" % version
)

log("Format version: " + str(version))
Expand All @@ -361,7 +364,7 @@ def decode_barcode(self, data):
# subfile designator
# FIXME could also be 'ID'
assert data[19:21] == "DL" or data[19:21] == "ID", (
"Not a driver's license (Got '%s', should be 'DL')" % data[19:21]
"Not a driver's license (Got '%s', should be 'DL')" % data[19:21]
)
offset = data[21:25]
assert offset.isdigit(), "Subfile offset is not an integer"
Expand Down Expand Up @@ -480,7 +483,7 @@ def decode_barcode(self, data):
pprint.pprint(subfile)

assert subfile[0][:2] == "DL" or subfile[0][:2] == "ID", (
"Not a driver's license (Got '%s', should be 'DL')" % subfile[0][:2]
"Not a driver's license (Got '%s', should be 'DL')" % subfile[0][:2]
)
subfile[0] = subfile[0][2:] # remove prepended "DL"
subfile[-1] = subfile[-1].strip(segterm)
Expand Down Expand Up @@ -660,7 +663,7 @@ def _decode_barcode_v3(self, fields, issueIdentifier):

# Physical description
sex = fields["DBC"]
assert sex in "12", "Invalid sex"
assert sex in "129", "Invalid sex"
if sex == "1":
sex = MALE
if sex == "2":
Expand All @@ -687,7 +690,8 @@ def _decode_barcode_v3(self, fields, issueIdentifier):
try: # Indiana puts it in the jurisdiction field ZIJ
height = fields["ZIJ"].split("-")
units = IMPERIAL
height = Height((int(height[0]) * 12) + int(height[1]), format="USA")
height = Height(
(int(height[0]) * 12) + int(height[1]), format="USA")
except KeyError:
# Give up on parsing height
log("ERROR: Unable to parse height.")
Expand All @@ -704,7 +708,8 @@ def _decode_barcode_v3(self, fields, issueIdentifier):
except KeyError:
try: # Indiana
hair = fields["ZIL"]
assert hair in HAIRCOLOURS, "Invalid hair colour: {0}".format(hair)
assert hair in HAIRCOLOURS, "Invalid hair colour: {0}".format(
hair)
except KeyError:
hair = None

Expand All @@ -724,7 +729,8 @@ def _decode_barcode_v3(self, fields, issueIdentifier):
except KeyError:
try: # Indiana again
weight = fields["ZIK"]
assert weight.isdigit(), "Weight is non-integer: {0}".format(weight)
assert weight.isdigit(
), "Weight is non-integer: {0}".format(weight)
weight = Weight(int(weight), format="USA")
except KeyError:
weight = None # Give up
Expand Down Expand Up @@ -817,11 +823,13 @@ def _decode_barcode_v4(self, fields, issueIdentifier):

# Physical description
sex = fields["DBC"]
assert sex in "12", "Invalid sex"
assert sex in "129", "Invalid sex"
if sex == "1":
sex = MALE
if sex == "2":
sex = FEMALE
if sex == "9":
sex = NOT_SPECIFIED

height = fields["DAU"]
if height[-2:].lower() == "cm": # metric
Expand Down Expand Up @@ -853,7 +861,7 @@ def _decode_barcode_v4(self, fields, issueIdentifier):
weight = Weight(None, int(fields["DAW"]), "USA")
except KeyError:
weight = None
if weight == None:
if weight is None:
# Try weight range
try:
weight = fields["DCE"]
Expand Down Expand Up @@ -958,11 +966,13 @@ def _decode_barcode_v5(self, fields, issueIdentifier):

# Physical description
sex = fields["DBC"]
assert sex in "12", "Invalid sex"
assert sex in "129", "Invalid sex"
if sex == "1":
sex = MALE
if sex == "2":
sex = FEMALE
if sex == "9":
sex == NOT_SPECIFIED

height = fields["DAU"]
if height[-2:] == "in": # inches
Expand All @@ -987,7 +997,7 @@ def _decode_barcode_v5(self, fields, issueIdentifier):
weight = Weight(None, int(fields["DAW"]), "USA")
except KeyError:
weight = None
if weight == None:
if weight is None:
# Try weight range
try:
weight = fields["DCE"]
Expand Down Expand Up @@ -1105,6 +1115,8 @@ def _decode_barcode_v6(self, fields, issueIdentifier): # 2011 standard
sex = MALE
if sex == "2":
sex = FEMALE
if sex == "9":
sex = NOT_SPECIFIED

eyes = fields["DAY"] # (REQUIRED 2011 k.)
assert eyes in EYECOLOURS, "Invalid eye colour: {0}".format(eyes)
Expand Down Expand Up @@ -1134,7 +1146,7 @@ def _decode_barcode_v6(self, fields, issueIdentifier): # 2011 standard
weight = Weight(None, int(fields["DAW"]), "USA")
except KeyError:
weight = None
if weight == None:
if weight is None:
# Try weight range
try:
weight = fields["DCE"]
Expand Down Expand Up @@ -1253,11 +1265,13 @@ def _decode_barcode_v8(self, fields, issueIdentifier): # 2013 standard

# Physical description
sex = fields["DBC"] # (REQUIRED 2013 j.)
assert sex in "12", "Invalid sex"
assert sex in "129", "Invalid sex"
if sex == "1":
sex = MALE
if sex == "2":
sex = FEMALE
if sex == "9":
sex = NOT_SPECIFIED

eyes = fields["DAY"] # (REQUIRED 2013 k.)
assert eyes in EYECOLOURS, "Invalid eye colour: {0}".format(eyes)
Expand Down Expand Up @@ -1413,7 +1427,7 @@ def _decode_barcode_v9(self, fields, issueIdentifier): # 2016 standard
if sex == "2":
sex = FEMALE
if sex == "9":
sex = UNSPECIFIED
sex = NOT_SPECIFIED

eyes = fields["DAY"] # (REQUIRED 2016 k.)
assert eyes in EYECOLOURS, "Invalid eye colour: {0}".format(eyes)
Expand Down Expand Up @@ -1443,7 +1457,7 @@ def _decode_barcode_v9(self, fields, issueIdentifier): # 2016 standard
weight = Weight(None, int(fields["DAW"]), "USA")
except KeyError:
weight = None
if weight == None:
if weight is None:
# Try weight range
try:
weight = fields["DCE"]
Expand Down Expand Up @@ -1623,7 +1637,7 @@ def __init__(self, weight_range, weight=None, format="ISO"):
else:
raise WeightError("Invalid format: '%s'" % format)

if weight_range == None: # Defined by exact weight (lbs or kg)
if weight_range is None: # Defined by exact weight (lbs or kg)
self.exact = True
assert weight != None and type(weight) == int, "Invalid weight"
self.weight = weight
Expand Down
Loading

0 comments on commit 471032e

Please sign in to comment.