Skip to content

Commit 6796690

Browse files
committed
Add support for importing Manjusri txt format.
1 parent 5d2d028 commit 6796690

File tree

15 files changed

+1907
-13
lines changed

15 files changed

+1907
-13
lines changed

README.md

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Yahoo! 奇摩輸入法 (KeyKey) 使用者資料庫解密 Swift Package。
2323
## 功能
2424

2525
- 🔓 解密 SQLite SEE AES-128 加密的使用者資料庫 (`SmartMandarinUserData.db`)
26+
- 📝 解析 MJSR(Manjusri 文殊)匯出文字檔案(奇摩輸入法匯出格式)
2627
- 🔤 解碼注音符號 (Bopomofo) qstring 欄位
2728
- 📖 讀取使用者詞彙資料(單元圖 (Unigram)、雙元圖 (Bigram)、候選字覆蓋)
2829
- 🔄 支援 `Sequence``AsyncSequence` 迭代
@@ -42,7 +43,9 @@ KeyKeyUserDBKit/
4243
│ │ ├── Gram.swift # 語料結構體
4344
│ │ ├── PhonaSet.swift # 注音符號處理
4445
│ │ ├── SEEDecryptor.swift # SQLite SEE AES-128 解密器
45-
│ │ └── UserDatabase.swift # 使用者資料庫讀取器
46+
│ │ ├── UserDatabase.swift # 使用者資料庫讀取器
47+
│ │ ├── UserPhraseDataSource.swift # 資料來源協定
48+
│ │ └── UserPhraseTextFileObj.swift # MJSR 匯出檔案解析器
4649
│ └── KeyKeyDecryptCLI/ # 命令列工具 (kkdecrypt)
4750
│ └── main.swift
4851
└── Tests/
@@ -130,6 +133,43 @@ for await gram in db.async {
130133
}
131134
```
132135

136+
### 解析 MJSR 匯出檔案
137+
138+
奇摩輸入法的匯出功能會產生 MJSR(Manjusri 文殊)格式的文字檔案,其中包含使用者單字詞及加密的 database block:
139+
140+
```swift
141+
import KeyKeyUserDBKit
142+
143+
// 從檔案載入 MJSR 匯出檔
144+
let textFile = try KeyKeyUserDBKit.UserPhraseTextFileObj(path: "export.txt")
145+
146+
// 或從 URL 載入
147+
let textFile = try KeyKeyUserDBKit.UserPhraseTextFileObj(url: fileURL)
148+
149+
// 取得所有語料資料(與 UserDatabase 相同的 API)
150+
let allGrams = try textFile.fetchAllGrams()
151+
152+
for gram in allGrams {
153+
print("\(gram.current)\(gram.keyArray.joined(separator: "-"))")
154+
}
155+
156+
// UserDatabase 與 UserPhraseTextFileObj 都實作 UserPhraseDataSource 協定
157+
// 可以統一處理不同資料來源
158+
func processDataSource(_ source: some KeyKeyUserDBKit.UserPhraseDataSource) throws {
159+
for gram in source {
160+
print(gram.describe())
161+
}
162+
}
163+
164+
// 使用資料庫
165+
let db = try KeyKeyUserDBKit.UserDatabase(path: "decrypted.db")
166+
try processDataSource(db)
167+
168+
// 使用匯出檔案
169+
let textFile = try KeyKeyUserDBKit.UserPhraseTextFileObj(path: "export.txt")
170+
try processDataSource(textFile)
171+
```
172+
133173
### 注音解碼
134174

135175
```swift
@@ -172,6 +212,8 @@ swift run kkdecrypt SmartMandarinUserData.db decrypted.db
172212
| `KeyKeyUserDBKit.PhonaSet` | `PhonaSet` |
173213
| `KeyKeyUserDBKit.SEEDecryptor` | `SEEDecryptor` |
174214
| `KeyKeyUserDBKit.UserDatabase` | `UserDatabase` |
215+
| `KeyKeyUserDBKit.UserPhraseTextFileObj` | `UserPhraseTextFileObj` |
216+
| `KeyKeyUserDBKit.UserPhraseDataSource` | `IUserPhraseDataSource` |
175217
| `fetchUnigrams()` | `FetchUnigrams()` |
176218
| `fetchBigrams(limit:)` | `FetchBigrams(int? limit)` |
177219
| `fetchCandidateOverrides()` | `FetchCandidateOverrides()` |
@@ -209,6 +251,16 @@ order = (high_char - 48) * 79 + (low_char - 48)
209251
syllable = consonant | (middle << 5) | (vowel << 7) | (tone << 11)
210252
```
211253

254+
## MJSR 匯出格式
255+
256+
MJSR(Manjusri 文殊)是奇摩輸入法的匯出檔案格式:
257+
258+
- **Header**: `MJSR version 1.0.0`
259+
- **使用者單字詞**: Tab 分隔格式 (`word\treading\tprobability\tbackoff`)
260+
- **`<database>` block**: 十六進位編碼的加密 SQLite 資料庫
261+
- 包含 `user_bigram_cache``user_candidate_override_cache` 表格
262+
- 加密密鑰: `mjsrexportmjsrex`(16 bytes)
263+
212264
## 授權
213265

214266
本專案採用 [LGPL-3.0-or-later](LICENSES/preferred/LGPL-3.0-or-later) 授權。

Sources/KeyKeyUserDBKit/UserDatabase.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@
22
// ====================
33
// This code is released under the SPDX-License-Identifier: `LGPL-3.0-or-later`.
44

5+
import Foundation
6+
57
#if canImport(Darwin)
68
import SQLite3
79
#else
810
import CSQLite
911
#endif
10-
import Foundation
1112

1213
// MARK: - KeyKeyUserDBKit.UserDatabase
1314

1415
extension KeyKeyUserDBKit {
1516
/// 使用者資料庫讀取器
16-
public final class UserDatabase: Sendable {
17+
public final class UserDatabase: Sendable, UserPhraseDataSource {
1718
// MARK: Lifecycle
1819

1920
// MARK: - Initializers
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// (c) 2025 and onwards The vChewing Project (LGPL v3.0 License or later).
2+
// ====================
3+
// This code is released under the SPDX-License-Identifier: `LGPL-3.0-or-later`.
4+
5+
// MARK: - KeyKeyUserDBKit.UserPhraseDataSource
6+
7+
extension KeyKeyUserDBKit {
8+
/// 使用者詞庫資料來源協定
9+
///
10+
/// 此協定定義了讀取使用者詞庫資料的通用介面,
11+
/// 可由資料庫 (`UserDatabase`) 或文字檔案 (`UserPhraseTextFileObj`) 實作。
12+
public protocol UserPhraseDataSource: Sequence where Element == Gram {
13+
/// 候選字覆蓋記錄的預設權重
14+
static var candidateOverrideProbability: Double { get }
15+
16+
/// 讀取所有使用者單元圖
17+
/// - Returns: 單元圖陣列
18+
/// - Throws: 讀取過程中發生的錯誤
19+
func fetchUnigrams() throws -> [Gram]
20+
21+
/// 讀取使用者雙元圖快取
22+
/// - Parameter limit: 限制回傳筆數 (nil 表示全部)
23+
/// - Returns: 雙元圖陣列
24+
/// - Throws: 讀取過程中發生的錯誤
25+
func fetchBigrams(limit: Int?) throws -> [Gram]
26+
27+
/// 讀取候選字覆蓋快取
28+
/// - Returns: 候選字覆蓋陣列
29+
/// - Throws: 讀取過程中發生的錯誤
30+
func fetchCandidateOverrides() throws -> [Gram]
31+
32+
/// 讀取所有使用者資料,回傳包含所有 Unigram、Bigram 和 CandidateOverride 的陣列
33+
/// - Returns: 包含所有結果的 `[Gram]` 陣列
34+
/// - Throws: 讀取過程中發生的錯誤
35+
func fetchAllGrams() throws -> [Gram]
36+
}
37+
}
38+
39+
// MARK: - Default Implementation
40+
41+
extension KeyKeyUserDBKit.UserPhraseDataSource {
42+
/// 預設實作:讀取所有使用者資料
43+
public func fetchAllGrams() throws -> [KeyKeyUserDBKit.Gram] {
44+
var allGrams: [KeyKeyUserDBKit.Gram] = []
45+
allGrams.append(contentsOf: try fetchUnigrams())
46+
allGrams.append(contentsOf: try fetchBigrams(limit: nil))
47+
allGrams.append(contentsOf: try fetchCandidateOverrides())
48+
return allGrams
49+
}
50+
}

0 commit comments

Comments
 (0)