# 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
```toml
[dependencies]
mdict-rs = "0.1"
```
If you need LZO-compressed blocks:
```toml
[dependencies]
mdict-rs = { version = "0.1", features = ["lzo"] }
```
## Quick Start
### MDX lookup
```rust,no_run
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
```rust,no_run
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
```rust,no_run
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
```rust,no_run
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
```rust,no_run
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](COMMERCIAL-LICENSING.md).