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#[derive(Debug)]
10pub struct MdxFile {
11 inner: MdictFile,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct MdxRecord {
17 pub key: String,
18 pub text: String,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct MdxOrdinalKey {
24 pub ordinal: KeyOrdinal,
25 pub key: String,
26}
27
28pub struct MdxKeyIter<'a> {
30 file: &'a MdxFile,
31 block_index: usize,
32 entry_index: usize,
33 current_block: Option<Arc<Vec<DecodedKeyEntry>>>,
34}
35
36pub 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 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
47 Self::open_with_options(path, OpenOptions::default())
48 }
49
50 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 pub fn header(&self) -> &Header {
59 &self.inner.header
60 }
61
62 pub fn len(&self) -> u64 {
64 self.inner.len()
65 }
66
67 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 pub fn keys(&self) -> MdxKeyIter<'_> {
84 MdxKeyIter::new(self)
85 }
86
87 pub fn keys_with_ordinals(&self) -> MdxOrdinalKeyIter<'_> {
89 MdxOrdinalKeyIter::new(self)
90 }
91
92 pub fn key_at(&self, ordinal: KeyOrdinal) -> Result<Option<String>> {
94 self.inner.key_at_ordinal(ordinal)
95 }
96
97 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
205pub 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}