mdict-rs 0.1.4

Library-first Rust parser for MDict .mdx and .mdd dictionaries
Documentation

mdict-rs

mdict-rs is a library-first Rust parser for MDict .mdx and .mdd dictionaries.

The project currently targets the widely used v2.0 layout with a strong bias toward:

  • checked parsing over panics
  • lazy key / record block decoding
  • library APIs before CLI tooling
  • clean-room interoperability instead of GPL code translation

Status

mdict-rs is ready for an early public release in the 0.1.x range.

What the current release means:

  • the v2.0 parser path is implemented and tested against a local corpus of real dictionaries
  • the public API is intentionally small
  • format coverage is still conservative rather than “all historical MDict variants”

Supported Scope

Currently supported:

  • MDX v2.0
  • MDD v2.0
  • lazy lookup, key iteration, ordinal key access, and entry iteration
  • encrypted keyword-index dictionaries
  • passcode-aware keyword-header support in the API
  • block compression:
    • uncompressed
    • zlib
    • optional lzo feature
  • encodings:
    • UTF-8
    • UTF-16LE
    • GBK / GB18030-family
    • Big5

Currently not supported:

  • version 1.x section layouts
  • writer support
  • HTML / stylesheet rewriting
  • mmap or sidecar indexes

Installation

[dependencies]
mdict-rs = "0.1"

If you need LZO-compressed blocks:

[dependencies]
mdict-rs = { version = "0.1", features = ["lzo"] }

Quick Start

MDX lookup

use mdict_rs::MdxFile;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dict = MdxFile::open("dictionary.mdx")?;
    if let Some(record) = dict.lookup("apple")? {
        println!("{}", record.text);
    }
    Ok(())
}

MDD resource lookup

use mdict_rs::MddFile;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dict = MddFile::open("dictionary.mdd")?;
    if let Some(resource) = dict.lookup("\\\\LM5style.css")? {
        println!("{} bytes", resource.data.len());
    }
    Ok(())
}

Key-only iteration

use mdict_rs::MdxFile;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dict = MdxFile::open("dictionary.mdx")?;
    for key in dict.keys().take(10) {
        println!("{}", key?);
    }
    Ok(())
}

Use entries() when you also need the decoded MDX text or MDD payload for each item.

Stable key ordinals

use mdict_rs::{KeyOrdinal, MdxFile};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dict = MdxFile::open("dictionary.mdx")?;
    for entry in dict.keys_with_ordinals().take(3) {
        let entry = entry?;
        println!("{}: {}", entry.ordinal.get(), entry.key);
    }

    let key = dict.key_at(KeyOrdinal::from(42))?;
    println!("{key:?}");
    Ok(())
}

Batched ordinal resolution

use mdict_rs::{KeyOrdinal, MdxFile};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let dict = MdxFile::open("dictionary.mdx")?;
    let keys = dict.keys_at(&[
        KeyOrdinal::from(7),
        KeyOrdinal::from(42),
        KeyOrdinal::from(7),
    ])?;
    println!("{keys:?}");
    Ok(())
}

Validation

The current codebase is verified with:

  • unit tests for crypto, compression, and header parsing
  • malformed-input tests
  • mutation-style regression tests
  • ignored integration tests against a large local sample
  • ignored integration tests against the full local dictionary corpus
  • a separate fuzz/ crate with libFuzzer entrypoints

License

This repository is publicly available under AGPL-3.0-only.

Commercial licensing is also available from the copyright holder for users who want to embed mdict-rs without AGPL obligations.

See COMMERCIAL-LICENSING.md.