mdict-rs 0.1.4

Library-first Rust parser for MDict .mdx and .mdd dictionaries
Documentation
use std::path::Path;
use std::sync::Arc;

use crate::core::{DecodedKeyEntry, MdictFile};
use crate::error::{Error, Result};
use crate::types::{ContainerKind, Header, KeyOrdinal, OpenOptions};

/// Opened `.mdd` resource dictionary file.
#[derive(Debug)]
pub struct MddFile {
    inner: MdictFile,
}

/// A decoded MDD key/resource pair.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MddResource {
    pub key: String,
    pub data: Vec<u8>,
}

/// A lazily readable MDD record span.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MddResourceSpan {
    pub key: String,
    pub record_start: u64,
    pub record_end: u64,
}

impl MddResourceSpan {
    pub fn len(&self) -> u64 {
        self.record_end.saturating_sub(self.record_start)
    }
}

/// A decoded MDD key with its stable ordinal.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MddOrdinalKey {
    pub ordinal: KeyOrdinal,
    pub key: String,
}

/// A lazy iterator over decoded MDD keys without reading resource payloads.
pub struct MddKeyIter<'a> {
    file: &'a MddFile,
    block_index: usize,
    entry_index: usize,
    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
}

/// A lazy iterator over decoded MDD keys with stable ordinals.
pub struct MddOrdinalKeyIter<'a> {
    file: &'a MddFile,
    block_index: usize,
    entry_index: usize,
    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
}

impl MddFile {
    /// Opens an MDD file with default options.
    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
        Self::open_with_options(path, OpenOptions::default())
    }

    /// Opens an MDD file with explicit options.
    pub fn open_with_options(path: impl AsRef<Path>, options: OpenOptions) -> Result<Self> {
        Ok(Self {
            inner: MdictFile::open(path, ContainerKind::Mdd, &options)?,
        })
    }

    /// Returns the parsed top-level header metadata.
    pub fn header(&self) -> &Header {
        &self.inner.header
    }

    /// Returns the number of resource entries declared by the file.
    pub fn len(&self) -> u64 {
        self.inner.len()
    }

    /// Performs a lazy lookup for a single resource key.
    pub fn lookup(&self, key: &str) -> Result<Option<MddResource>> {
        let Some(span) = self.lookup_span(key)? else {
            return Ok(None);
        };
        let mut data = Vec::with_capacity(span.len() as usize);
        self.read_record_span_with(&span, |chunk| {
            data.extend_from_slice(chunk);
            Ok(())
        })?;
        Ok(Some(MddResource {
            key: span.key,
            data,
        }))
    }

    /// Resolves a single resource key into a lazily readable record span.
    pub fn lookup_span(&self, key: &str) -> Result<Option<MddResourceSpan>> {
        let Some((block_index, entry_index, entries)) = self.inner.find_entry(key)? else {
            return Ok(None);
        };
        let entry = &entries[entry_index];
        let record_end = self.inner.record_end(block_index, entry_index, &entries)?;
        Ok(Some(MddResourceSpan {
            key: entry.key.clone(),
            record_start: entry.record_start,
            record_end,
        }))
    }

    /// Reads a resolved resource span chunk by chunk.
    pub fn read_record_span_with<F>(&self, span: &MddResourceSpan, visitor: F) -> Result<()>
    where
        F: FnMut(&[u8]) -> Result<()>,
    {
        self.inner
            .visit_record_span(span.record_start, span.record_end, visitor)
    }

    /// Returns a lazy iterator over decoded keys without reading resources.
    pub fn keys(&self) -> MddKeyIter<'_> {
        MddKeyIter::new(self)
    }

    /// Returns a lazy iterator over decoded keys with stable ordinals.
    pub fn keys_with_ordinals(&self) -> MddOrdinalKeyIter<'_> {
        MddOrdinalKeyIter::new(self)
    }

    /// Returns the original key at a stable ordinal without reading resources.
    pub fn key_at(&self, ordinal: KeyOrdinal) -> Result<Option<String>> {
        self.inner.key_at_ordinal(ordinal)
    }

    /// Returns original keys for a batch of ordinals in input order.
    pub fn keys_at(&self, ordinals: &[KeyOrdinal]) -> Result<Vec<Option<String>>> {
        self.inner.keys_at_ordinals(ordinals)
    }

    pub fn entries(&self) -> MddEntryIter<'_> {
        MddEntryIter::new(self)
    }
}

impl<'a> MddKeyIter<'a> {
    fn new(file: &'a MddFile) -> Self {
        Self {
            file,
            block_index: 0,
            entry_index: 0,
            current_block: None,
        }
    }
}

impl<'a> MddOrdinalKeyIter<'a> {
    fn new(file: &'a MddFile) -> Self {
        Self {
            file,
            block_index: 0,
            entry_index: 0,
            current_block: None,
        }
    }
}

impl Iterator for MddKeyIter<'_> {
    type Item = Result<String>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.block_index >= self.file.inner.key_block_count() {
                return None;
            }

            if self.current_block.is_none() {
                match self.file.inner.decode_key_block(self.block_index) {
                    Ok(entries) => {
                        self.current_block = Some(entries);
                        self.entry_index = 0;
                    }
                    Err(error) => return Some(Err(error)),
                }
            }

            let entries = self.current_block.as_ref().unwrap();
            if self.entry_index >= entries.len() {
                self.block_index += 1;
                self.current_block = None;
                continue;
            }

            let key = entries[self.entry_index].key.clone();
            self.entry_index += 1;
            return Some(Ok(key));
        }
    }
}

impl Iterator for MddOrdinalKeyIter<'_> {
    type Item = Result<MddOrdinalKey>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.block_index >= self.file.inner.key_block_count() {
                return None;
            }

            if self.current_block.is_none() {
                match self.file.inner.decode_key_block(self.block_index) {
                    Ok(entries) => {
                        self.current_block = Some(entries);
                        self.entry_index = 0;
                    }
                    Err(error) => return Some(Err(error)),
                }
            }

            let entries = self.current_block.as_ref().unwrap();
            if self.entry_index >= entries.len() {
                self.block_index += 1;
                self.current_block = None;
                continue;
            }

            let block = &self.file.inner.key_index.blocks[self.block_index];
            let result = (|| -> Result<MddOrdinalKey> {
                let ordinal = block
                    .entry_start_index
                    .checked_add(self.entry_index as u64)
                    .ok_or(Error::InvalidFormat("key ordinal overflow"))?;
                Ok(MddOrdinalKey {
                    ordinal: KeyOrdinal::from(ordinal),
                    key: entries[self.entry_index].key.clone(),
                })
            })();
            self.entry_index += 1;
            return Some(result);
        }
    }
}

/// Lazy iterator over decoded MDD resources.
pub struct MddEntryIter<'a> {
    file: &'a MddFile,
    block_index: usize,
    entry_index: usize,
    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
}

impl<'a> MddEntryIter<'a> {
    fn new(file: &'a MddFile) -> Self {
        Self {
            file,
            block_index: 0,
            entry_index: 0,
            current_block: None,
        }
    }
}

impl Iterator for MddEntryIter<'_> {
    type Item = Result<MddResource>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.block_index >= self.file.inner.key_block_count() {
                return None;
            }

            if self.current_block.is_none() {
                match self.file.inner.decode_key_block(self.block_index) {
                    Ok(entries) => {
                        self.current_block = Some(entries);
                        self.entry_index = 0;
                    }
                    Err(error) => return Some(Err(error)),
                }
            }

            let entries = self.current_block.as_ref().unwrap();
            if self.entry_index >= entries.len() {
                self.block_index += 1;
                self.current_block = None;
                continue;
            }

            let entry = &entries[self.entry_index];
            let result = (|| -> Result<MddResource> {
                let record_end =
                    self.file
                        .inner
                        .record_end(self.block_index, self.entry_index, entries)?;
                let data = self
                    .file
                    .inner
                    .read_record_span(entry.record_start, record_end)?;
                Ok(MddResource {
                    key: entry.key.clone(),
                    data,
                })
            })();
            self.entry_index += 1;
            return Some(result);
        }
    }
}