Skip to main content

mdict_rs/
mdx.rs

1use std::path::Path;
2use std::sync::Arc;
3
4use crate::core::{DecodedKeyEntry, MdictFile};
5use crate::error::{Error, Result};
6use crate::types::{ContainerKind, Header, KeyOrdinal, OpenOptions};
7
8/// Opened `.mdx` dictionary file.
9#[derive(Debug)]
10pub struct MdxFile {
11    inner: MdictFile,
12}
13
14/// A decoded MDX key/value record.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct MdxRecord {
17    pub key: String,
18    pub text: String,
19}
20
21/// A decoded MDX key with its stable ordinal.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct MdxOrdinalKey {
24    pub ordinal: KeyOrdinal,
25    pub key: String,
26}
27
28/// A lazy iterator over decoded MDX keys without reading record payloads.
29pub struct MdxKeyIter<'a> {
30    file: &'a MdxFile,
31    block_index: usize,
32    entry_index: usize,
33    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
34}
35
36/// A lazy iterator over decoded MDX keys with stable ordinals.
37pub struct MdxOrdinalKeyIter<'a> {
38    file: &'a MdxFile,
39    block_index: usize,
40    entry_index: usize,
41    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
42}
43
44impl MdxFile {
45    /// Opens an MDX file with default options.
46    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
47        Self::open_with_options(path, OpenOptions::default())
48    }
49
50    /// Opens an MDX file with explicit options.
51    pub fn open_with_options(path: impl AsRef<Path>, options: OpenOptions) -> Result<Self> {
52        Ok(Self {
53            inner: MdictFile::open(path, ContainerKind::Mdx, &options)?,
54        })
55    }
56
57    /// Returns the parsed top-level header metadata.
58    pub fn header(&self) -> &Header {
59        &self.inner.header
60    }
61
62    /// Returns the number of dictionary entries declared by the file.
63    pub fn len(&self) -> u64 {
64        self.inner.len()
65    }
66
67    /// Performs a lazy lookup for a single MDX key.
68    pub fn lookup(&self, key: &str) -> Result<Option<MdxRecord>> {
69        let Some((block_index, entry_index, entries)) = self.inner.find_entry(key)? else {
70            return Ok(None);
71        };
72        let entry = &entries[entry_index];
73        let end = self.inner.record_end(block_index, entry_index, &entries)?;
74        let bytes = self.inner.read_record_span(entry.record_start, end)?;
75        let text = decode_record_text(self.inner.key_encoding, &bytes)?;
76        Ok(Some(MdxRecord {
77            key: entry.key.clone(),
78            text,
79        }))
80    }
81
82    /// Returns a lazy iterator over decoded keys without reading records.
83    pub fn keys(&self) -> MdxKeyIter<'_> {
84        MdxKeyIter::new(self)
85    }
86
87    /// Returns a lazy iterator over decoded keys with stable ordinals.
88    pub fn keys_with_ordinals(&self) -> MdxOrdinalKeyIter<'_> {
89        MdxOrdinalKeyIter::new(self)
90    }
91
92    /// Returns the original key at a stable ordinal without reading records.
93    pub fn key_at(&self, ordinal: KeyOrdinal) -> Result<Option<String>> {
94        self.inner.key_at_ordinal(ordinal)
95    }
96
97    /// Returns original keys for a batch of ordinals in input order.
98    pub fn keys_at(&self, ordinals: &[KeyOrdinal]) -> Result<Vec<Option<String>>> {
99        self.inner.keys_at_ordinals(ordinals)
100    }
101
102    pub fn entries(&self) -> MdxEntryIter<'_> {
103        MdxEntryIter::new(self)
104    }
105}
106
107impl<'a> MdxKeyIter<'a> {
108    fn new(file: &'a MdxFile) -> Self {
109        Self {
110            file,
111            block_index: 0,
112            entry_index: 0,
113            current_block: None,
114        }
115    }
116}
117
118impl<'a> MdxOrdinalKeyIter<'a> {
119    fn new(file: &'a MdxFile) -> Self {
120        Self {
121            file,
122            block_index: 0,
123            entry_index: 0,
124            current_block: None,
125        }
126    }
127}
128
129impl Iterator for MdxKeyIter<'_> {
130    type Item = Result<String>;
131
132    fn next(&mut self) -> Option<Self::Item> {
133        loop {
134            if self.block_index >= self.file.inner.key_block_count() {
135                return None;
136            }
137
138            if self.current_block.is_none() {
139                match self.file.inner.decode_key_block(self.block_index) {
140                    Ok(entries) => {
141                        self.current_block = Some(entries);
142                        self.entry_index = 0;
143                    }
144                    Err(error) => return Some(Err(error)),
145                }
146            }
147
148            let entries = self.current_block.as_ref().unwrap();
149            if self.entry_index >= entries.len() {
150                self.block_index += 1;
151                self.current_block = None;
152                continue;
153            }
154
155            let key = entries[self.entry_index].key.clone();
156            self.entry_index += 1;
157            return Some(Ok(key));
158        }
159    }
160}
161
162impl Iterator for MdxOrdinalKeyIter<'_> {
163    type Item = Result<MdxOrdinalKey>;
164
165    fn next(&mut self) -> Option<Self::Item> {
166        loop {
167            if self.block_index >= self.file.inner.key_block_count() {
168                return None;
169            }
170
171            if self.current_block.is_none() {
172                match self.file.inner.decode_key_block(self.block_index) {
173                    Ok(entries) => {
174                        self.current_block = Some(entries);
175                        self.entry_index = 0;
176                    }
177                    Err(error) => return Some(Err(error)),
178                }
179            }
180
181            let entries = self.current_block.as_ref().unwrap();
182            if self.entry_index >= entries.len() {
183                self.block_index += 1;
184                self.current_block = None;
185                continue;
186            }
187
188            let block = &self.file.inner.key_index.blocks[self.block_index];
189            let result = (|| -> Result<MdxOrdinalKey> {
190                let ordinal = block
191                    .entry_start_index
192                    .checked_add(self.entry_index as u64)
193                    .ok_or(Error::InvalidFormat("key ordinal overflow"))?;
194                Ok(MdxOrdinalKey {
195                    ordinal: KeyOrdinal::from(ordinal),
196                    key: entries[self.entry_index].key.clone(),
197                })
198            })();
199            self.entry_index += 1;
200            return Some(result);
201        }
202    }
203}
204
205/// Lazy iterator over decoded MDX entries.
206pub struct MdxEntryIter<'a> {
207    file: &'a MdxFile,
208    block_index: usize,
209    entry_index: usize,
210    current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
211}
212
213impl<'a> MdxEntryIter<'a> {
214    fn new(file: &'a MdxFile) -> Self {
215        Self {
216            file,
217            block_index: 0,
218            entry_index: 0,
219            current_block: None,
220        }
221    }
222}
223
224impl Iterator for MdxEntryIter<'_> {
225    type Item = Result<MdxRecord>;
226
227    fn next(&mut self) -> Option<Self::Item> {
228        loop {
229            if self.block_index >= self.file.inner.key_block_count() {
230                return None;
231            }
232
233            if self.current_block.is_none() {
234                match self.file.inner.decode_key_block(self.block_index) {
235                    Ok(entries) => {
236                        self.current_block = Some(entries);
237                        self.entry_index = 0;
238                    }
239                    Err(error) => return Some(Err(error)),
240                }
241            }
242
243            let entries = self.current_block.as_ref().unwrap();
244            if self.entry_index >= entries.len() {
245                self.block_index += 1;
246                self.current_block = None;
247                continue;
248            }
249
250            let entry = &entries[self.entry_index];
251            let result = (|| -> Result<MdxRecord> {
252                let end =
253                    self.file
254                        .inner
255                        .record_end(self.block_index, self.entry_index, entries)?;
256                let bytes = self.file.inner.read_record_span(entry.record_start, end)?;
257                let text = decode_record_text(self.file.inner.key_encoding, &bytes)?;
258                Ok(MdxRecord {
259                    key: entry.key.clone(),
260                    text,
261                })
262            })();
263            self.entry_index += 1;
264            return Some(result);
265        }
266    }
267}
268
269fn decode_record_text(encoding: crate::encoding::TextEncoding, bytes: &[u8]) -> Result<String> {
270    let text = encoding.decode(bytes, "mdx record")?;
271    Ok(text.trim_end_matches('\0').to_owned())
272}