Easydict/Swift/Service/Dictionary/MDict/mdict-overview.md
MDict 实现用户导入的 MDX/MDD 离线词典查询能力。它负责管理词典文件、解析二进制索引、
查询条目内容、重写词典内资源链接,并把结果交给共享的词典 HTML 渲染层展示。
MDict/
├── MDictService.swift # 查询服务入口和结果 HTML 包装
├── MDictConfigurationView.swift # 设置页导入、启用、排序和删除 UI
├── MDictManager.swift # 导入记录持久化、加载和生命周期管理
├── MDictDictionary.swift # 单本词典查询、MDD 资源解析和链接重写
├── MDictSearchIndex.swift # 变形词、prefix、substring 和 fuzzy fallback
├── MDictReader/ # MDX/MDD 二进制 reader、parser 和底层工具
├── mdict-overview.md # 本目录说明
└── mdict-architecture.svg
MDictService 是 QueryService 子类,负责读取启用词典、收集查询结果,并调用
DictionaryHTMLRenderer 生成结果面板 HTML。MDictConfigurationView 是设置页 UI,负责触发 MDX/MDD 导入,并把启用、排序和删除操作
转发给 MDictManager。MDictManager 保存导入记录到 Defaults,查询时按需在后台加载启用的
MDictDictionary 实例,并在记录变化时发出通知。MDictDictionary 表示一本 MDX 词典和它的 MDD 资源集合,负责查词、查资源、把图片、音频、
CSS 和脚本资源重写为 WebKit 可加载的形式;MDD reader 会在首次资源查询时懒加载,并缓存
常用 data URI、解析后的 CSS、MDX 同目录同名 CSS 文件和资源缺失结果。MDictSearchIndex 在精确查词失败后按需建立轻量 headword 索引,提供变形词、前缀、词头
substring 和小编辑距离 fuzzy fallback,不索引正文 HTML;超大词库会跳过这个同步 fallback,
避免单次 miss 触发全量 key block 解析。MDictReader/ 子目录只处理 MDX/MDD 二进制格式,不处理 UI、服务配置或结果面板样式。导入流程从 MDictConfigurationView 的文件选择器开始,MDictManager 根据扩展名导入 MDX
或匹配 MDD,合并同名资源文件并保存 MDictDictionaryRecord。MDD 匹配保留精确同名优先,
只把短数字后缀当作 multipart 资源后缀处理,避免年份等普通文件名误挂到无关 MDX。文件选择器
使用 MDX/MDD 扩展名类型并以通用 data 类型兜底,避免系统未注册自定义扩展名时没有可选类型。
首次查询启用词典时,MDictManager 在后台创建缺失的 MDictDictionary,重叠查询会复用同一个
in-flight 加载任务,避免重复解析同一 MDX;返回结果按 records 中的用户排序组织,保证查询
优先级和显示顺序一致。
查询流程从 MDictService.translate 开始。服务读取启用的 MDictDictionary,在调用方任务里
逐本调用 lookup(保留结构化并发取消语义,外层 translate 取消时下一本词典前会立即停止),
词典内部先通过 MDictReader 精确查找 key entry 和 record block;如果无结果,再尝试常见
英文变形词,并按需构建 headword 搜索索引用于 prefix、substring 和 fuzzy fallback。
超大词库只走精确查词和变形词路径,避免一次查询 miss 阻塞 UI。
命中后再把 HTML 中的本地资源链接改写为 data URI 或内部锚点。只有命中结果需要解析 MDD
资源时,词典才会创建对应的 resource reader。图片、音频和 CSS url 生成的 data URI 会按 LRU
缓存;外链 stylesheet 解析后的 CSS 也会缓存,并复用已缓存的内嵌资源。资源 key 查询会先尝试
词条原始路径,再生成反斜杠和前导斜杠变体,兼容不同 MDD 生成器的路径风格。CSS 和脚本等文本
资源优先使用 MDX header encoding 解码,再回退到常见 Unicode 编码;如果 CSS 内部资源受
单次 data URI 预算影响未完全替换,这份结果不会进入 stylesheet 缓存。最终服务把每本词典
的 HTML section 交给
DictionaryHTMLRenderer,由共享词典结果模板渲染。
如果 MDX 文件旁边存在同名 .css,例如 concise-enhanced.mdx 对应
concise-enhanced.css,MDictDictionary 会在首次命中 HTML 查询时读取并缓存该样式,然后
注入到词条内容前面,适配 MDict 词库常见的外置样式优化文件。
资源改写会优先处理脚本、stylesheet、图片和 CSS url,再处理音频链接;srcset 候选写入
data URI 时会把其中的 , 转义为 %2C,避免被浏览器误当作候选分隔符切碎。外链脚本被
改写成内联时会保留原 <script> 上的属性(如 type="module"、defer),只剥离 src
和原有 nonce,再注入结果页 CSP 所需的 easydict-mdict nonce,并转义 </script 片段,
避免 HTML parser 提前截断脚本。普通
sound:// 和 mdict-sound:// 链接都会被改写为 data URI,避免 click handler 把无 scheme
处理器的 URL 喂给 new Audio(...) 而播放失败;负向前缀确保不会重复改写已被替换过的位置。
javascript:new Audio(...) 中的本地音频会先被改写为 data URI,并由结果页 click handler
拦截播放,避免被 CSP 当作导航阻断。
mdict-entry:// 跳转由结果面板按 href payload 查询,而不是依赖锚点展示文本。
单次查询有 data URI 数量和总字节预算,避免大词条因为数百个发音资源被同步内联而阻塞查询。
MDictManager.loadErrors。MDictManager.dictionariesForLookup()、词典大小写设置、key block
边界和 MDictSearchIndex fallback candidates。MDictDictionary 的 resource key candidates、同名
.css 文件路径、资源重写和 data URI/CSS cache 命中。MDictReader/ 子目录里的 MDictReader、MDictBinary、
MDictKeyBlocks 和 MDictRecords 开始定位。MDictService.wrapWithStyle 与 DictionaryHTMLRenderer 排查。