mdict/
mdx.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3use std::fs::File;
4use std::io::BufReader;
5use std::path::PathBuf;
6use encoding_rs::{Encoding, UTF_16LE};
7use crate::parser::{decode_slice_string, load, lookup_record};
8use crate::{Error, Result};
9
10pub type Reader = BufReader<File>;
11
12pub trait KeyMaker {
13	fn make(&self, key: &Cow<str>, resource: bool) -> String;
14}
15
16impl<F> KeyMaker for F where F: Fn(&Cow<str>, bool) -> String {
17	#[inline]
18	fn make(&self, key: &Cow<str>, resource: bool) -> String
19	{
20		self(key, resource)
21	}
22}
23
24pub struct MDict<M: KeyMaker> {
25	pub(crate) mdx: Mdx,
26	pub(crate) resources: Vec<Mdx>,
27	pub(crate) key_maker: M,
28}
29
30pub struct Mdx {
31	pub(crate) encoding: &'static Encoding,
32	pub(crate) title: String,
33	#[allow(unused)]
34	pub(crate) encrypted: u8,
35	pub(crate) key_entries: Vec<KeyEntry>,
36	pub(crate) records_info: Vec<BlockEntryInfo>,
37	pub(crate) reader: Reader,
38	pub(crate) record_block_offset: u64,
39	pub(crate) record_cache: Option<HashMap<usize, Vec<u8>>>,
40}
41
42#[derive(Debug)]
43pub(crate) struct KeyEntry {
44	pub(crate) offset: usize,
45	pub(crate) text: String,
46}
47
48#[derive(Debug)]
49pub(crate) struct BlockEntryInfo {
50	pub(crate) compressed_size: usize,
51	pub(crate) decompressed_size: usize,
52}
53
54#[derive(Debug)]
55pub(crate) struct RecordOffset {
56	pub(crate) buf_offset: usize,
57	pub(crate) block_offset: usize,
58	pub(crate) record_size: usize,
59	pub(crate) decomp_size: usize,
60}
61
62#[derive(Debug)]
63pub struct WordDefinition<'a> {
64	pub key: &'a str,
65	pub definition: String,
66}
67
68impl<M: KeyMaker> MDict<M> {
69	pub fn lookup<'a>(&mut self, word: &'a str) -> Result<Option<WordDefinition<'a>>>
70	{
71		let encoding = self.mdx.encoding;
72		let key = self.key_maker.make(&Cow::Borrowed(word), false);
73		if let Some(slice) = lookup_record(&mut self.mdx, &key)? {
74			let definition = decode_slice_string(&slice, encoding)?.0.to_string();
75			Ok(Some(WordDefinition { key: word, definition }))
76		} else {
77			Ok(None)
78		}
79	}
80
81	pub fn get_resource(&mut self, path: &str) -> Result<Option<Cow<[u8]>>>
82	{
83		let key = self.key_maker.make(&Cow::Borrowed(path), true);
84		for mdx in &mut self.resources {
85			if let Some(slice) = lookup_record(mdx, &key)? {
86				return Ok(Some(slice));
87			}
88		}
89		Ok(None)
90	}
91
92	pub fn title(&self) -> &str
93	{
94		&self.mdx.title
95	}
96}
97
98pub struct MDictBuilder {
99	path: PathBuf,
100	cache_definition: bool,
101	cache_resource: bool,
102}
103
104impl MDictBuilder {
105	pub fn new(path: impl Into<PathBuf>) -> Self
106	{
107		MDictBuilder {
108			path: path.into(),
109			cache_definition: false,
110			cache_resource: false,
111		}
112	}
113
114	#[inline]
115	pub fn cache_definition(mut self, cache: bool) -> Self
116	{
117		self.cache_definition = cache;
118		self
119	}
120	#[inline]
121	pub fn cache_resource(mut self, cache: bool) -> Self
122	{
123		self.cache_resource = cache;
124		self
125	}
126	#[inline]
127	pub fn build(self) -> Result<MDict<impl KeyMaker>>
128	{
129		self.build_with_key_maker(|key: &Cow<str>, _resource: bool| key.to_ascii_lowercase())
130	}
131	pub fn build_with_key_maker<M: KeyMaker>(self, key_maker: M)
132		-> Result<MDict<M>>
133	{
134		let path = self.path;
135		let f = File::open(&path)?;
136		let reader = BufReader::new(f);
137		let cwd = path.parent()
138			.ok_or_else(|| Error::InvalidPath(path.clone()))?
139			.canonicalize()?;
140		let mdx = load(
141			reader,
142			UTF_16LE,
143			self.cache_definition,
144			&key_maker,
145			false)?;
146		let filename = path.file_stem()
147			.ok_or_else(|| Error::InvalidPath(path.clone()))?
148			.to_str()
149			.ok_or_else(|| Error::InvalidPath(path.clone()))?;
150		let resources = load_resources(
151			&cwd,
152			filename,
153			self.cache_resource,
154			&key_maker)?;
155		Ok(MDict {
156			mdx,
157			resources,
158			key_maker,
159		})
160	}
161}
162
163fn load_resources(cwd: &PathBuf, name: &str, cache_resources: bool,
164	key_maker: &dyn KeyMaker) -> Result<Vec<Mdx>>
165{
166	let mut resources = vec![];
167	// <filename>.mdd first
168	let path = cwd.join(format!("{}.mdd", name));
169	if !path.exists() {
170		return Ok(resources);
171	}
172	let f = File::open(&path)?;
173	let reader = BufReader::new(f);
174	resources.push(load(
175		reader,
176		UTF_16LE,
177		cache_resources,
178		key_maker,
179		true)?);
180
181	// filename.n.mdd then
182	let mut i = 1;
183	loop {
184		let path = cwd.join(format!("{}.{}.mdd", name, i));
185		if !path.exists() {
186			break;
187		}
188		let f = File::open(&path)?;
189		let reader = BufReader::new(f);
190		resources.push(load(
191			reader,
192			UTF_16LE,
193			cache_resources,
194			key_maker,
195			true)?);
196		i += 1;
197	}
198	Ok(resources)
199}