///|
pub(all) struct Spec {
layout : Layout
document_code : String
issuer : String
primary_name : String
secondary_name : String
document_number : String
nationality : String
birth : String
sex : String
expiry : String
optional : String
optional_secondary : String
} derive(Eq, Debug)
///|
fn encode_code(code : String) -> String {
pad_fillers(upper_ascii(spaces_to_fillers(trim_ascii(code))), 2)
}
///|
fn encode_alpha3(code : String) -> String {
pad_fillers(upper_ascii(spaces_to_fillers(trim_ascii(code))), 3)
}
///|
fn encode_name_field(
primary : String,
secondary : String,
width : Int,
) -> String {
let p = spaces_to_fillers(upper_ascii(trim_ascii(primary)))
let s = spaces_to_fillers(upper_ascii(trim_ascii(secondary)))
let body = if s.length() == 0 { p } else { p + "<<" + s }
pad_fillers(body, width)
}
///|
fn encode_doc(num : String) -> String {
pad_fillers(upper_ascii(spaces_to_fillers(trim_ascii(num))), 9)
}
///|
fn encode_sex(sex : String) -> String {
let t = upper_ascii(trim_ascii(sex))
if t.length() == 0 {
"<"
} else {
unit_at(t, 0)
}
}
///|
fn must_digit(field : String) -> String {
match check_digit_char(field) {
None => "0"
Some(ch) => ch
}
}
///|
fn build_td3(spec : Spec, diags : Array[Diagnostic]) -> Array[String] {
let l1 = encode_code(spec.document_code) +
encode_alpha3(spec.issuer) +
encode_name_field(spec.primary_name, spec.secondary_name, 39)
let doc = encode_doc(spec.document_number)
let birth = pad_fillers(spec.birth, 6)
let expiry = pad_fillers(spec.expiry, 6)
let optional = pad_fillers(
upper_ascii(spaces_to_fillers(trim_ascii(spec.optional))),
14,
)
let sex = encode_sex(spec.sex)
let doc_cd = must_digit(doc)
let birth_cd = must_digit(birth)
let expiry_cd = must_digit(expiry)
let optional_cd = must_digit(optional)
let composite = doc +
doc_cd +
birth +
birth_cd +
expiry +
expiry_cd +
optional +
optional_cd
let l2 = doc +
doc_cd +
encode_alpha3(spec.nationality) +
birth +
birth_cd +
sex +
expiry +
expiry_cd +
optional +
optional_cd +
must_digit(composite)
if spec.primary_name.length() == 0 {
diags.push(error_diag("MRZ013", "primary name is missing", 1, 6, 5, 44))
}
if !is_valid_yymmdd(birth) {
diags.push(
error_diag(
"MRZ010", "date of birth is not a valid YYMMDD value", 2, 14, 13, 19,
),
)
}
if !is_valid_yymmdd(expiry) {
diags.push(
error_diag(
"MRZ010", "date of expiry is not a valid YYMMDD value", 2, 22, 21, 27,
),
)
}
if !is_valid_sex(sex) {
diags.push(error_diag("MRZ011", "sex must be M, F, or <", 2, 21, 20, 21))
}
[l1, l2]
}
///|
fn build_td2(spec : Spec, diags : Array[Diagnostic]) -> Array[String] {
let l1 = encode_code(spec.document_code) +
encode_alpha3(spec.issuer) +
encode_name_field(spec.primary_name, spec.secondary_name, 31)
let doc = encode_doc(spec.document_number)
let birth = pad_fillers(spec.birth, 6)
let expiry = pad_fillers(spec.expiry, 6)
let optional = pad_fillers(
upper_ascii(spaces_to_fillers(trim_ascii(spec.optional))),
7,
)
let sex = encode_sex(spec.sex)
let doc_cd = must_digit(doc)
let birth_cd = must_digit(birth)
let expiry_cd = must_digit(expiry)
let composite = doc +
doc_cd +
birth +
birth_cd +
expiry +
expiry_cd +
optional
if spec.primary_name.length() == 0 {
diags.push(error_diag("MRZ013", "primary name is missing", 1, 6, 5, 36))
}
if !is_valid_yymmdd(birth) {
diags.push(
error_diag(
"MRZ010", "date of birth is not a valid YYMMDD value", 2, 14, 13, 19,
),
)
}
[
l1,
doc +
doc_cd +
encode_alpha3(spec.nationality) +
birth +
birth_cd +
sex +
expiry +
expiry_cd +
optional +
must_digit(composite),
]
}
///|
fn build_td1(spec : Spec, diags : Array[Diagnostic]) -> Array[String] {
let doc = encode_doc(spec.document_number)
let opt1 = pad_fillers(
upper_ascii(spaces_to_fillers(trim_ascii(spec.optional))),
15,
)
let l1 = encode_code(spec.document_code) +
encode_alpha3(spec.issuer) +
doc +
must_digit(doc) +
opt1
let birth = pad_fillers(spec.birth, 6)
let expiry = pad_fillers(spec.expiry, 6)
let opt2 = pad_fillers(
upper_ascii(spaces_to_fillers(trim_ascii(spec.optional_secondary))),
11,
)
let sex = encode_sex(spec.sex)
let l2body = birth +
must_digit(birth) +
sex +
expiry +
must_digit(expiry) +
encode_alpha3(spec.nationality) +
opt2
let composite = l1[5:30].to_owned() +
l2body[0:7].to_owned() +
l2body[8:15].to_owned() +
l2body[18:29].to_owned()
let l2 = l2body + must_digit(composite)
let l3 = encode_name_field(spec.primary_name, spec.secondary_name, 30)
if spec.primary_name.length() == 0 {
diags.push(error_diag("MRZ013", "primary name is missing", 3, 1, 0, 30))
}
if !is_valid_yymmdd(birth) {
diags.push(
error_diag(
"MRZ010", "date of birth is not a valid YYMMDD value", 2, 1, 0, 6,
),
)
}
[l1, l2, l3]
}
///|
/// Build canonical MRZ lines from structured fields. Check digits are computed.
pub fn build(spec : Spec) -> (String, Array[Diagnostic]) {
let diags : Array[Diagnostic] = []
if spec.layout != TD1 && spec.optional_secondary.length() > 0 {
diags.push(
warning_diag(
"MRZ016", "optional_secondary is only encoded for TD1", 1, 1, 0, 0,
),
)
}
let lines = match spec.layout {
TD3 => build_td3(spec, diags)
TD2 => build_td2(spec, diags)
TD1 => build_td1(spec, diags)
}
let mut out = lines[0]
let mut i = 1
while i < lines.length() {
out = out + "\n" + lines[i]
i += 1
}
(out, diags)
}
///|
pub fn build_text(spec : Spec) -> String? {
let (text, diags) = build(spec)
let mut i = 0
while i < diags.length() {
if diags[i].severity == Error {
return None
}
i += 1
}
Some(text)
}