moonmrz

    Pure MoonBit ICAO 9303 MRZ parser, 7-3-1 check-digit verifier, and TD1/TD2/TD3 builder

    mrz
    icao9303
    passport
    td1
    td3
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    23 hours ago
    Downloads
    2

    #MoonMRZ

    纯 MoonBit 的 ICAO 9303 机读区(MRZ)库:解析、7-3-1 校验位核验、TD1/TD2/TD3 重建。

    MoonMRZ 读取旅行证件 MRZ 文本(护照 TD3 2×44、签证/证件 TD2 2×36、身份证 TD1 3×30),校验 ICAO 字符集与加权校验位,抽出签发国/姓名/证件号/国籍/出生/性别/有效期/可选数据,并可按字段重建规范行。它不做 OCR、芯片 BAC/PACE、人脸比对或在线身份核验。

    • GitHub:https://github.com/ZJH-666-ZJH/moonmrz
    • 包名:ZJH-666-ZJH/moonmrz
    • 版本:0.1.0

    #安装

    moon add ZJH-666-ZJH/moonmrz

    尚未发布到 mooncakes.io 时,请依赖 Git 仓库。可运行入口:

    moon run cmd/demo --target wasm-gc

    #三个示例

    cmd/demo/main.mbt 中的验收场景一致。

    #1. 航司 DCS:从护照 MRZ 写入旅客姓名

    输入 ICAO TD3 样例,抽出 ERIKSSON ANNA MARIA 和证件号 L898902C3,写入值机记录。

    let filed = @mrz.parse(passport).unwrap()
    // filed.full_name() == "ERIKSSON ANNA MARIA"

    #2. 酒店自助机:拒绝错误校验位并给出列定位

    证件号校验位被改成 0。解析失败,诊断指向第 2 行第 10 列的 MRZ005,闸机拒绝放行。parse_with_diagnostics 仍返回已抽出的字段,便于提示旅客。

    let (doc, diags) = @mrz.parse_with_diagnostics(tampered)
    // diags[0].format() == "2:10: error MRZ005: document number check digit is wrong"

    #3. 测试夹具:按字段重建 TD3 并往返解析

    Spec 生成规范 MRZ,再 parse 回去,两行文本与 ICAO 样例一致。

    let rebuilt = @mrz.build_text(spec).unwrap()
    let again = @mrz.parse(rebuilt).unwrap()
    // format(again) == original TD3 text

    Demo 预期输出:

    DCS: ERIKSSON ANNA MARIA / L898902C3 KIOSK: 2:10: error MRZ005: document number check digit is wrong KIOSK-FIELDS: L898902C3 FIXTURE: rebuilt TD3 round-trip ok MoonMRZ acceptance: 3 scenarios passed

    #API 入口

    let doc = @mrz.parse(text).unwrap()
    let (maybe, diags) = @mrz.parse_with_diagnostics(text)
    let ok = @mrz.is_valid(text)
    let layout = @mrz.layout_of(text)
    let (built, warnings) = @mrz.build(spec)

    示例假定调用包将本地库导入为 @mrz;可运行配置见 cmd/demo/moon.pkg。ICAO 样例文本在 examples/

    #测试

    moon check --target wasm-gc --deny-warn moon test --target wasm-gc --deny-warn moon run cmd/demo --target wasm-gc

    本地无 C 编译器时不做 native 测试;CI 在 Ubuntu 上执行 moon check --target native --deny-warn

    #边界

    • 不是 OCR / 图像切行
    • 不是护照芯片 BAC、PACE 或 SOD 验签
    • 不是人脸比对或在线身份提供方
    • 证件号超过 9 个字符时,构建器按 ICAO 字段宽度截断,不自动溢到可选区

    #License

    MIT

    Diagnostic

    pub(all) struct Diagnostic {
    code : String
    severity : Severity
    message : String
    span : Span
    } derive(Eq,
    Debug
    )

    Diagnostic::format

    fn Diagnostic::format(self : Diagnostic) -> String

    Document

    pub(all) struct Document {
    layout : Layout
    document_code : String
    issuer : String
    names : Names
    document_number : String
    nationality : String
    birth : String
    sex : String
    expiry : String
    optional : String
    optional_secondary : String
    raw_lines : Array[String]
    } derive(Eq,
    Debug
    )

    Immutable MRZ snapshot. Arrays returned by accessors are copies.

    Document::full_name

    fn Document::full_name(self : Document) -> String

    Document::lines

    fn Document::lines(self : Document) -> Array[String]

    Document::to_spec

    fn Document::to_spec(self : Document) -> Spec

    Rebuild a Spec from a parsed document so callers can edit fields and encode again.

    Layout

    pub(all) enum Layout {
    TD1
    TD2
    TD3
    } derive(Eq,
    Debug
    )

    Names

    pub(all) struct Names {
    primary : String
    secondary : String
    } derive(Eq,
    Debug
    )

    Severity

    pub(all) enum Severity {
    Error
    Warning
    } derive(Eq,
    Debug
    )

    Span

    pub(all) struct Span {
    start : Int
    end : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    Half-open UTF-16 offsets. Line numbers are 1-based.

    Spec

    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
    )

    build

    fn build(spec : Spec) -> (String, Array[Diagnostic])

    Build canonical MRZ lines from structured fields. Check digits are computed.

    build_text

    fn build_text(spec : Spec) -> String?

    check_digit

    fn check_digit(field : String) -> Int?

    ICAO 9303 7-3-1 check digit for a field that already uses MRZ characters.

    check_digit_char

    fn check_digit_char(field : String) -> String?

    format

    fn format(doc : Document) -> String

    has_error

    fn has_error(diags : Array[Diagnostic]) -> Bool

    is_valid

    fn is_valid(input : String) -> Bool

    layout_of

    fn layout_of(input : String) -> Layout?

    Detect TD1/TD2/TD3 from line lengths after CRLF splitting and 88/72/90 unfolding.

    parse

    fn parse(input : String) -> Document?

    parse_with_diagnostics

    fn parse_with_diagnostics(input : String) -> (Document?, Array[Diagnostic])

    Parse MRZ text and always return diagnostics. A document is present even when check-digit errors were recorded, so callers can inspect extracted fields.