Repo // Add SQLite support for factory database.
This commit is contained in:
parent
40d866714e
commit
133901ede2
|
@ -9,6 +9,7 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import SQLite3
|
||||
|
||||
// MARK: - 前導工作
|
||||
|
||||
|
@ -27,6 +28,19 @@ fileprivate extension String {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - String as SQL Command
|
||||
|
||||
fileprivate extension String {
|
||||
@discardableResult func runAsSQLExec(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
|
||||
ptrDB != nil && sqlite3_exec(ptrDB, self, nil, nil, nil) == SQLITE_OK
|
||||
}
|
||||
|
||||
@discardableResult func runAsSQLPreparedStep(dbPointer ptrDB: inout OpaquePointer?, stmtPtr ptrStmt: inout OpaquePointer?) -> Bool {
|
||||
guard ptrDB != nil else { return false }
|
||||
return sqlite3_prepare_v2(ptrDB, self, -1, &ptrStmt, nil) == SQLITE_OK && sqlite3_step(ptrStmt) == SQLITE_DONE
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - StringView Ranges Extension (by Isaac Xen)
|
||||
|
||||
fileprivate extension String {
|
||||
|
@ -117,40 +131,112 @@ func cnvPhonabetToASCII(_ incoming: String) -> String {
|
|||
|
||||
private let urlCurrentFolder = URL(fileURLWithPath: FileManager.default.currentDirectoryPath)
|
||||
|
||||
private let urlCHSRoot: String = "./components/chs/"
|
||||
private let urlCHTRoot: String = "./components/cht/"
|
||||
private let urlCHSRoot: String = "\(urlCurrentFolder.path)/components/chs/"
|
||||
private let urlCHTRoot: String = "\(urlCurrentFolder.path)/components/cht/"
|
||||
|
||||
private let urlKanjiCore: String = "./components/common/char-kanji-core.txt"
|
||||
private let urlMiscBPMF: String = "./components/common/char-misc-bpmf.txt"
|
||||
private let urlMiscNonKanji: String = "./components/common/char-misc-nonkanji.txt"
|
||||
private let urlKanjiCore: String = "\(urlCurrentFolder.path)/components/common/char-kanji-core.txt"
|
||||
private let urlMiscBPMF: String = "\(urlCurrentFolder.path)/components/common/char-misc-bpmf.txt"
|
||||
private let urlMiscNonKanji: String = "\(urlCurrentFolder.path)/components/common/char-misc-nonkanji.txt"
|
||||
|
||||
private let urlPunctuation: String = "./components/common/data-punctuations.txt"
|
||||
private let urlSymbols: String = "./components/common/data-symbols.txt"
|
||||
private let urlZhuyinwen: String = "./components/common/data-zhuyinwen.txt"
|
||||
private let urlCNS: String = "./components/common/char-kanji-cns.txt"
|
||||
private let urlPunctuation: String = "\(urlCurrentFolder.path)/components/common/data-punctuations.txt"
|
||||
private let urlSymbols: String = "\(urlCurrentFolder.path)/components/common/data-symbols.txt"
|
||||
private let urlZhuyinwen: String = "\(urlCurrentFolder.path)/components/common/data-zhuyinwen.txt"
|
||||
private let urlCNS: String = "\(urlCurrentFolder.path)/components/common/char-kanji-cns.txt"
|
||||
|
||||
private let urlOutputCHS: String = "./data-chs.txt"
|
||||
private let urlOutputCHT: String = "./data-cht.txt"
|
||||
private let urlOutputCHS: String = "\(urlCurrentFolder.path)/data-chs.txt"
|
||||
private let urlOutputCHT: String = "\(urlCurrentFolder.path)/data-cht.txt"
|
||||
|
||||
private let urlJSONSymbols: String = "./data-symbols.json"
|
||||
private let urlJSONZhuyinwen: String = "./data-zhuyinwen.json"
|
||||
private let urlJSONCNS: String = "./data-cns.json"
|
||||
private let urlJSONSymbols: String = "\(urlCurrentFolder.path)/data-symbols.json"
|
||||
private let urlJSONZhuyinwen: String = "\(urlCurrentFolder.path)/data-zhuyinwen.json"
|
||||
private let urlJSONCNS: String = "\(urlCurrentFolder.path)/data-cns.json"
|
||||
|
||||
private let urlJSONCHS: String = "./data-chs.json"
|
||||
private let urlJSONCHT: String = "./data-cht.json"
|
||||
private let urlJSONBPMFReverseLookup: String = "./data-bpmf-reverse-lookup.json"
|
||||
private let urlJSONBPMFReverseLookupCNS1: String = "./data-bpmf-reverse-lookup-CNS1.json"
|
||||
private let urlJSONBPMFReverseLookupCNS2: String = "./data-bpmf-reverse-lookup-CNS2.json"
|
||||
private let urlJSONBPMFReverseLookupCNS3: String = "./data-bpmf-reverse-lookup-CNS3.json"
|
||||
private let urlJSONBPMFReverseLookupCNS4: String = "./data-bpmf-reverse-lookup-CNS4.json"
|
||||
private let urlJSONBPMFReverseLookupCNS5: String = "./data-bpmf-reverse-lookup-CNS5.json"
|
||||
private let urlJSONBPMFReverseLookupCNS6: String = "./data-bpmf-reverse-lookup-CNS6.json"
|
||||
private let urlJSONCHS: String = "\(urlCurrentFolder.path)/data-chs.json"
|
||||
private let urlJSONCHT: String = "\(urlCurrentFolder.path)/data-cht.json"
|
||||
private let urlJSONBPMFReverseLookup: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup.json"
|
||||
private let urlJSONBPMFReverseLookupCNS1: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS1.json"
|
||||
private let urlJSONBPMFReverseLookupCNS2: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS2.json"
|
||||
private let urlJSONBPMFReverseLookupCNS3: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS3.json"
|
||||
private let urlJSONBPMFReverseLookupCNS4: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS4.json"
|
||||
private let urlJSONBPMFReverseLookupCNS5: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS5.json"
|
||||
private let urlJSONBPMFReverseLookupCNS6: String = "\(urlCurrentFolder.path)/data-bpmf-reverse-lookup-CNS6.json"
|
||||
|
||||
private var isReverseLookupDictionaryProcessed: Bool = false
|
||||
|
||||
private let urlSQLite: String = "\(urlCurrentFolder.path)/Build/Release/vChewingFactoryDatabase.sqlite"
|
||||
|
||||
private var mapReverseLookupForCheck: [String: [String]] = [:]
|
||||
private var exceptedChars: Set<String> = .init()
|
||||
|
||||
private var ptrSQL: OpaquePointer?
|
||||
private var sharedJSONEncoder: JSONEncoder = .init()
|
||||
|
||||
var rangeMapJSONCHS: [String: [String]] = [:]
|
||||
var rangeMapJSONCHT: [String: [String]] = [:]
|
||||
var rangeMapSymbols: [String: [String]] = [:]
|
||||
var rangeMapZhuyinwen: [String: [String]] = [:]
|
||||
var rangeMapCNS: [String: [String]] = [:]
|
||||
var rangeMapReverseLookup: [String: [String]] = [:]
|
||||
/// Also use mapReverseLookupForCheck.
|
||||
|
||||
// MARK: - 準備資料庫
|
||||
|
||||
func prepareDatabase() -> Bool {
|
||||
let sqlMakeTableMACV = """
|
||||
DROP TABLE IF EXISTS DATA_REV;
|
||||
DROP TABLE IF EXISTS DATA_MAIN;
|
||||
CREATE TABLE IF NOT EXISTS DATA_MAIN (
|
||||
theKey TEXT NOT NULL,
|
||||
theDataCHS TEXT,
|
||||
theDataCHT TEXT,
|
||||
theDataCNS TEXT,
|
||||
theDataMISC TEXT,
|
||||
theDataSYMB TEXT,
|
||||
theDataCHEW TEXT,
|
||||
PRIMARY KEY (theKey)
|
||||
) WITHOUT ROWID;
|
||||
CREATE TABLE IF NOT EXISTS DATA_REV (
|
||||
theChar TEXT NOT NULL,
|
||||
theReadings TEXT NOT NULL,
|
||||
PRIMARY KEY (theChar)
|
||||
) WITHOUT ROWID;
|
||||
"""
|
||||
guard sqlite3_open(urlSQLite, &ptrSQL) == SQLITE_OK else { return false }
|
||||
guard sqlite3_exec(ptrSQL, "PRAGMA synchronous = OFF;", nil, nil, nil) == SQLITE_OK else { return false }
|
||||
guard sqlite3_exec(ptrSQL, "PRAGMA journal_mode = MEMORY;", nil, nil, nil) == SQLITE_OK else { return false }
|
||||
guard sqlMakeTableMACV.runAsSQLExec(dbPointer: &ptrSQL) else { return false }
|
||||
guard "begin;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult func writeMainMapToSQL(_ theMap: [String: [String]], column columnName: String, ptrStmt: inout OpaquePointer?) -> Bool {
|
||||
for (encryptedKey, arrValues) in theMap {
|
||||
// SQL 語言需要對西文 ASCII 半形單引號做回退處理、變成「''」。
|
||||
let safeKey = encryptedKey.replacingOccurrences(of: "'", with: "''")
|
||||
let valueText = arrValues.joined(separator: "\t").replacingOccurrences(of: "'", with: "''")
|
||||
let sqlStmt = "INSERT INTO DATA_MAIN (theKey, \(columnName)) VALUES ('\(safeKey)', '\(valueText)') ON CONFLICT(theKey) DO UPDATE SET \(columnName)='\(valueText)';"
|
||||
guard sqlStmt.runAsSQLPreparedStep(dbPointer: &ptrSQL, stmtPtr: &ptrStmt) else {
|
||||
print("Failed: " + sqlStmt)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult func writeRevLookupMapToSQL(_ theMap: [String: [String]], ptrStmt: inout OpaquePointer?) -> Bool {
|
||||
for (encryptedKey, arrValues) in theMap {
|
||||
// SQL 語言需要對西文 ASCII 半形單引號做回退處理、變成「''」。
|
||||
let safeKey = encryptedKey.replacingOccurrences(of: "'", with: "''")
|
||||
let valueText = arrValues.joined(separator: "\t").replacingOccurrences(of: "'", with: "''")
|
||||
let sqlStmt = "INSERT INTO DATA_REV (theChar, theReadings) VALUES ('\(safeKey)', '\(valueText)') ON CONFLICT(theChar) DO UPDATE SET theReadings='\(valueText)';"
|
||||
guard sqlStmt.runAsSQLPreparedStep(dbPointer: &ptrSQL, stmtPtr: &ptrStmt) else {
|
||||
print("Failed: " + sqlStmt)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - 載入詞組檔案且輸出陣列
|
||||
|
||||
func rawDictForPhrases(isCHS: Bool) -> [Unigram] {
|
||||
|
@ -459,10 +545,8 @@ func fileOutput(isCHS: Bool) {
|
|||
let i18n: String = isCHS ? "簡體中文" : "繁體中文"
|
||||
var strPunctuation = ""
|
||||
var rangeMapJSON: [String: [String]] = [:]
|
||||
let pathOutput = urlCurrentFolder.appendingPathComponent(
|
||||
isCHS ? urlOutputCHS : urlOutputCHT)
|
||||
let jsonURL = urlCurrentFolder.appendingPathComponent(
|
||||
isCHS ? urlJSONCHS : urlJSONCHT)
|
||||
let pathOutput = URL(fileURLWithPath: isCHS ? urlOutputCHS : urlOutputCHT)
|
||||
let jsonURL = URL(fileURLWithPath: isCHS ? urlJSONCHS : urlJSONCHT)
|
||||
var strPrintLine = ""
|
||||
// 讀取標點內容
|
||||
do {
|
||||
|
@ -533,10 +617,15 @@ func fileOutput(isCHS: Bool) {
|
|||
do {
|
||||
try strPrintLine.write(to: pathOutput, atomically: true, encoding: .utf8)
|
||||
try JSONSerialization.data(withJSONObject: rangeMapJSON, options: .sortedKeys).write(to: jsonURL)
|
||||
if isCHS {
|
||||
rangeMapJSONCHS = rangeMapJSON
|
||||
} else {
|
||||
rangeMapJSONCHT = rangeMapJSON
|
||||
}
|
||||
} catch {
|
||||
NSLog(" - \(i18n): Error on writing strings to file: \(error)")
|
||||
}
|
||||
NSLog(" - \(i18n): 寫入完成。")
|
||||
NSLog(" - \(i18n): JSON & TXT 寫入完成。")
|
||||
if !arrFoundedDuplications.isEmpty {
|
||||
NSLog(" - \(i18n): 尋得下述重複項目,請務必手動排查:")
|
||||
print("-------------------")
|
||||
|
@ -576,7 +665,9 @@ func commonFileOutput() {
|
|||
let theKey = String(neta[1])
|
||||
let theValue = String(neta[0])
|
||||
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
|
||||
mapSymbols[cnvPhonabetToASCII(theKey), default: []].append(theValue)
|
||||
let encryptedKey = cnvPhonabetToASCII(theKey)
|
||||
mapSymbols[encryptedKey, default: []].append(theValue)
|
||||
rangeMapSymbols[encryptedKey, default: []].append(theValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -587,7 +678,9 @@ func commonFileOutput() {
|
|||
let theKey = String(neta[1])
|
||||
let theValue = String(neta[0])
|
||||
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
|
||||
mapZhuyinwen[cnvPhonabetToASCII(theKey), default: []].append(theValue)
|
||||
let encryptedKey = cnvPhonabetToASCII(theKey)
|
||||
mapZhuyinwen[encryptedKey, default: []].append(theValue)
|
||||
rangeMapZhuyinwen[encryptedKey, default: []].append(theValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -598,30 +691,33 @@ func commonFileOutput() {
|
|||
let theKey = String(neta[1])
|
||||
let theValue = String(neta[0])
|
||||
if !neta[0].isEmpty, !neta[1].isEmpty, line.first != "#" {
|
||||
mapCNS[cnvPhonabetToASCII(theKey), default: []].append(theValue)
|
||||
let encryptedKey = cnvPhonabetToASCII(theKey)
|
||||
mapCNS[encryptedKey, default: []].append(theValue)
|
||||
rangeMapCNS[encryptedKey, default: []].append(theValue)
|
||||
json: if !theKey.contains("_"), !theKey.contains("-") {
|
||||
rangeMapReverseLookup[theValue, default: []].append(encryptedKey)
|
||||
if mapReverseLookupCNS1.keys.count <= 16500 {
|
||||
mapReverseLookupCNS1[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS1[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
if mapReverseLookupCNS2.keys.count <= 16500 {
|
||||
mapReverseLookupCNS2[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS2[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
if mapReverseLookupCNS3.keys.count <= 16500 {
|
||||
mapReverseLookupCNS3[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS3[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
if mapReverseLookupCNS4.keys.count <= 16500 {
|
||||
mapReverseLookupCNS4[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS4[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
if mapReverseLookupCNS5.keys.count <= 16500 {
|
||||
mapReverseLookupCNS5[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS5[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
if mapReverseLookupCNS6.keys.count <= 16500 {
|
||||
mapReverseLookupCNS6[theValue, default: []].append(cnvPhonabetToASCII(theKey))
|
||||
mapReverseLookupCNS6[theValue, default: []].append(encryptedKey)
|
||||
break json
|
||||
}
|
||||
}
|
||||
|
@ -654,38 +750,6 @@ func commonFileOutput() {
|
|||
NSLog(" - \(i18n): 寫入完成。")
|
||||
}
|
||||
|
||||
// MARK: - 主執行緒
|
||||
|
||||
func main() {
|
||||
let globalQueue = DispatchQueue.global(qos: .default)
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯符號表情ㄅ文語料檔案。")
|
||||
commonFileOutput()
|
||||
group.leave()
|
||||
}
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯繁體中文核心語料檔案。")
|
||||
fileOutput(isCHS: false)
|
||||
group.leave()
|
||||
}
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯簡體中文核心語料檔案。")
|
||||
fileOutput(isCHS: true)
|
||||
group.leave()
|
||||
}
|
||||
// 一直等待完成
|
||||
_ = group.wait(timeout: .distantFuture)
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
NSLog("// 全部辭典檔案建置完畢。")
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
// MARK: - 辭庫健康狀況檢查專用函式
|
||||
|
||||
func healthCheck(_ data: [Unigram]) -> String {
|
||||
|
@ -979,3 +1043,62 @@ func healthCheck(_ data: [Unigram]) -> String {
|
|||
result += "\n"
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - 主執行緒
|
||||
|
||||
func main() {
|
||||
guard prepareDatabase() else {
|
||||
NSLog("// SQLite 資料庫初期化失敗。")
|
||||
exit(-1)
|
||||
}
|
||||
let globalQueue = DispatchQueue.global(qos: .default)
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯符號表情ㄅ文語料檔案。")
|
||||
commonFileOutput()
|
||||
group.leave()
|
||||
}
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯繁體中文核心語料檔案。")
|
||||
fileOutput(isCHS: false)
|
||||
group.leave()
|
||||
}
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
NSLog("// 準備編譯簡體中文核心語料檔案。")
|
||||
fileOutput(isCHS: true)
|
||||
group.leave()
|
||||
}
|
||||
// 一直等待完成
|
||||
_ = group.wait(timeout: .distantFuture)
|
||||
NSLog("// 全部 TXT & JSON 辭典檔案建置完畢。")
|
||||
NSLog("// 開始整合反查資料。")
|
||||
mapReverseLookupForCheck.forEach { key, values in
|
||||
values.reversed().forEach { valueLiteral in
|
||||
let value = cnvPhonabetToASCII(valueLiteral)
|
||||
if !rangeMapReverseLookup[key, default: []].contains(value) {
|
||||
rangeMapReverseLookup[key, default: []].insert(value, at: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
NSLog("// 反查資料整合完畢。")
|
||||
NSLog("// 準備建置 SQL 資料庫。")
|
||||
var ptrStmt: OpaquePointer?
|
||||
writeMainMapToSQL(rangeMapJSONCHS, column: "theDataCHS", ptrStmt: &ptrStmt)
|
||||
writeMainMapToSQL(rangeMapJSONCHT, column: "theDataCHT", ptrStmt: &ptrStmt)
|
||||
writeMainMapToSQL(rangeMapSymbols, column: "theDataSYMB", ptrStmt: &ptrStmt)
|
||||
writeMainMapToSQL(rangeMapZhuyinwen, column: "theDataCHEW", ptrStmt: &ptrStmt)
|
||||
writeMainMapToSQL(rangeMapCNS, column: "theDataCNS", ptrStmt: &ptrStmt)
|
||||
writeRevLookupMapToSQL(rangeMapReverseLookup, ptrStmt: &ptrStmt)
|
||||
sqlite3_finalize(ptrStmt)
|
||||
let committed = "commit;".runAsSQLExec(dbPointer: &ptrSQL)
|
||||
assert(committed)
|
||||
let compressed = "VACUUM;".runAsSQLExec(dbPointer: &ptrSQL)
|
||||
assert(compressed)
|
||||
sqlite3_close_v2(ptrSQL)
|
||||
NSLog("// 全部 SQLite 辭典檔案建置完畢。")
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
*/
|
||||
|
||||
import Foundation
|
||||
import SQLite3
|
||||
|
||||
#if os(Linux)
|
||||
import Glibc
|
||||
|
@ -31,18 +32,58 @@ import Foundation
|
|||
import Darwin
|
||||
#endif
|
||||
|
||||
public enum DictType {
|
||||
case zhHantTW
|
||||
case zhHantHK
|
||||
case zhHansSG
|
||||
case zhHansJP
|
||||
case zhHantKX
|
||||
case zhHansCN
|
||||
public enum DictType: Int, CaseIterable {
|
||||
case zhHantTW = 0
|
||||
case zhHantHK = 1
|
||||
case zhHansSG = 2
|
||||
case zhHansJP = 3
|
||||
case zhHantKX = 4
|
||||
case zhHansCN = 5
|
||||
|
||||
public static func match(rawKeyString: String) -> DictType? {
|
||||
DictType.allCases.filter { $0.rawKeyString == rawKeyString }.first
|
||||
}
|
||||
|
||||
public var rawKeyString: String {
|
||||
switch self {
|
||||
case .zhHantTW:
|
||||
return "zh2TW"
|
||||
case .zhHantHK:
|
||||
return "zh2HK"
|
||||
case .zhHansSG:
|
||||
return "zh2SG"
|
||||
case .zhHansJP:
|
||||
return "zh2JP"
|
||||
case .zhHantKX:
|
||||
return "zh2KX"
|
||||
case .zhHansCN:
|
||||
return "zh2CN"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class HotenkaChineseConverter {
|
||||
private(set) var dict: [String: [String: String]]
|
||||
private var dictFiles: [String: [String]]
|
||||
var ptrSQL: OpaquePointer?
|
||||
var ptrStatement: OpaquePointer?
|
||||
|
||||
deinit {
|
||||
sqlite3_finalize(ptrStatement)
|
||||
sqlite3_close_v2(ptrSQL)
|
||||
ptrSQL = nil
|
||||
ptrStatement = nil
|
||||
}
|
||||
|
||||
public init(sqliteDir dbPath: String) {
|
||||
dict = .init()
|
||||
dictFiles = .init()
|
||||
guard sqlite3_open(dbPath, &ptrSQL) == SQLITE_OK else {
|
||||
NSLog("// Exception happened when connecting to SQLite database at: \(dbPath).")
|
||||
ptrSQL = nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public init(plistDir: String) {
|
||||
dictFiles = .init()
|
||||
|
@ -137,26 +178,23 @@ public class HotenkaChineseConverter {
|
|||
|
||||
// MARK: - Public Methods
|
||||
|
||||
public func convert(_ target: String, to dictType: DictType) -> String {
|
||||
var dictTypeKey: String
|
||||
|
||||
switch dictType {
|
||||
case .zhHantTW:
|
||||
dictTypeKey = "zh2TW"
|
||||
case .zhHantHK:
|
||||
dictTypeKey = "zh2HK"
|
||||
case .zhHansSG:
|
||||
dictTypeKey = "zh2SG"
|
||||
case .zhHansJP:
|
||||
dictTypeKey = "zh2JP"
|
||||
case .zhHantKX:
|
||||
dictTypeKey = "zh2KX"
|
||||
case .zhHansCN:
|
||||
dictTypeKey = "zh2CN"
|
||||
public func query(dict dictType: DictType, key searchKey: String) -> String? {
|
||||
guard ptrSQL != nil else { return dict[dictType.rawKeyString]?[searchKey] }
|
||||
let sqlQuery = "SELECT * FROM DATA_HOTENKA WHERE dict=\(dictType.rawValue) AND theKey='\(searchKey)';"
|
||||
sqlite3_prepare_v2(ptrSQL, sqlQuery, -1, &ptrStatement, nil)
|
||||
// 此處只需要用到第一筆結果。
|
||||
while sqlite3_step(ptrStatement) == SQLITE_ROW {
|
||||
guard let rawValue = sqlite3_column_text(ptrStatement, 2) else { continue }
|
||||
return String(cString: rawValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
public func convert(_ target: String, to dictType: DictType) -> String {
|
||||
var result = ""
|
||||
guard let useDict = dict[dictTypeKey] else { return target }
|
||||
if ptrSQL == nil {
|
||||
guard dict[dictType.rawKeyString] != nil else { return target }
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while i < (target.count) {
|
||||
|
@ -167,7 +205,7 @@ public class HotenkaChineseConverter {
|
|||
innerloop: while j > 0 {
|
||||
let start = target.index(target.startIndex, offsetBy: i)
|
||||
let end = target.index(target.startIndex, offsetBy: i + j)
|
||||
guard let useDictSubStr = useDict[String(target[start ..< end])] else {
|
||||
guard let useDictSubStr = query(dict: dictType, key: String(target[start ..< end])) else {
|
||||
j -= 1
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ let package = Package(
|
|||
.product(name: "Megrez", package: "vChewing_Megrez"),
|
||||
.product(name: "Shared", package: "vChewing_Shared"),
|
||||
.product(name: "PinyinPhonaConverter", package: "vChewing_PinyinPhonaConverter"),
|
||||
],
|
||||
resources: [
|
||||
.process("Resources/sequenceDataFromEtenDOS-chs.json"),
|
||||
.process("Resources/sequenceDataFromEtenDOS-cht.json"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
import SQLite3
|
||||
|
||||
public extension vChewingLM {
|
||||
/// 語言模組副本化模組(LMInstantiator,下稱「LMI」)自身為符合天權星組字引擎內
|
||||
|
@ -29,6 +30,15 @@ public extension vChewingLM {
|
|||
/// LMI 會根據需要分別載入原廠語言模組和其他個別的子語言模組。LMI 本身不會記錄這些
|
||||
/// 語言模組的相關資料的存放位置,僅藉由參數來讀取相關訊息。
|
||||
class LMInstantiator: LangModelProtocol {
|
||||
// SQLite 連線所在的記憶體位置。
|
||||
static var ptrSQL: OpaquePointer?
|
||||
|
||||
// SQLite 連線是否已經建立。
|
||||
public private(set) static var isSQLDBConnected: Bool = false
|
||||
|
||||
// SQLite Statement 專用的記憶體位置。
|
||||
static var ptrStatement: OpaquePointer?
|
||||
|
||||
// 在函式內部用以記錄狀態的開關。
|
||||
public var isCassetteEnabled = false
|
||||
public var isPhraseReplacementEnabled = false
|
||||
|
@ -43,6 +53,27 @@ public extension vChewingLM {
|
|||
self.isCHS = isCHS
|
||||
}
|
||||
|
||||
@discardableResult public static func connectSQLDB(dbPath: String, dropPreviousConnection: Bool = true) -> Bool {
|
||||
if dropPreviousConnection { disconnectSQLDB() }
|
||||
vCLog("Establishing SQLite connection to: \(dbPath)")
|
||||
guard sqlite3_open(dbPath, &Self.ptrSQL) == SQLITE_OK else { return false }
|
||||
guard "PRAGMA journal_mode = MEMORY;".runAsSQLExec(dbPointer: &ptrSQL) else { return false }
|
||||
isSQLDBConnected = true
|
||||
return true
|
||||
}
|
||||
|
||||
public static func disconnectSQLDB() {
|
||||
if Self.ptrStatement != nil {
|
||||
sqlite3_finalize(Self.ptrStatement)
|
||||
Self.ptrStatement = nil
|
||||
}
|
||||
if Self.ptrSQL != nil {
|
||||
sqlite3_close_v2(Self.ptrSQL)
|
||||
Self.ptrSQL = nil
|
||||
}
|
||||
isSQLDBConnected = false
|
||||
}
|
||||
|
||||
/// 介紹一下幾個通用的語言模組型別:
|
||||
/// ----------------------
|
||||
/// LMCoreEX 是全功能通用型的模組,每一筆辭典記錄以 key 為注音、以 [Unigram] 陣列作為記錄內容。
|
||||
|
@ -53,25 +84,6 @@ public extension vChewingLM {
|
|||
/// 但是,LMCoreEX 對 2010-2013 年等舊 mac 機種而言,讀取速度異常緩慢。
|
||||
/// 於是 LMCoreJSON 就出場了,專門用來讀取原廠的 JSON 格式的辭典。
|
||||
|
||||
// 聲明原廠語言模組:
|
||||
// Reverse 的話,第一欄是注音,第二欄是對應的漢字,第三欄是可能的權重。
|
||||
// 不 Reverse 的話,第一欄是漢字,第二欄是對應的注音,第三欄是可能的權重。
|
||||
var lmCore = LMCoreJSON(
|
||||
reverse: false, consolidate: false, defaultScore: -9.9, forceDefaultScore: false
|
||||
)
|
||||
var lmMisc = LMCoreJSON(
|
||||
reverse: true, consolidate: false, defaultScore: -1.0, forceDefaultScore: false
|
||||
)
|
||||
|
||||
// 簡體中文模式與繁體中文模式共用全字庫擴展模組,故靜態處理。
|
||||
// 不然,每個模式都會讀入一份全字庫,會多佔用 100MB 記憶體。
|
||||
static var lmCNS = vChewingLM.LMCoreJSON(
|
||||
reverse: true, consolidate: false, defaultScore: -11.0, forceDefaultScore: false
|
||||
)
|
||||
static var lmSymbols = vChewingLM.LMCoreJSON(
|
||||
reverse: true, consolidate: false, defaultScore: -13.0, forceDefaultScore: false
|
||||
)
|
||||
|
||||
// 磁帶資料模組。「currentCassette」對外唯讀,僅用來讀取磁帶本身的中繼資料(Metadata)。
|
||||
static var lmCassette = LMCassette()
|
||||
|
||||
|
@ -92,54 +104,7 @@ public extension vChewingLM {
|
|||
|
||||
// MARK: - 工具函式
|
||||
|
||||
public func resetFactoryJSONModels() {
|
||||
lmCore.clear()
|
||||
lmMisc.clear()
|
||||
Self.lmCNS.clear()
|
||||
Self.lmSymbols.clear()
|
||||
}
|
||||
|
||||
public var isCoreLMLoaded: Bool { lmCore.isLoaded }
|
||||
public func loadLanguageModel(json: (dict: [String: [String]]?, path: String)) {
|
||||
guard let jsonDict = json.dict else {
|
||||
vCLog("lmCore: File access failure: \(json.path)")
|
||||
return
|
||||
}
|
||||
lmCore.load((dict: jsonDict, path: json.path))
|
||||
vCLog("lmCore: \(lmCore.count) entries of data loaded from: \(json.path)")
|
||||
}
|
||||
|
||||
public var isCNSDataLoaded: Bool { Self.lmCNS.isLoaded }
|
||||
public func loadCNSData(json: (dict: [String: [String]]?, path: String)) {
|
||||
guard let jsonDict = json.dict else {
|
||||
vCLog("lmCNS: File access failure: \(json.path)")
|
||||
return
|
||||
}
|
||||
Self.lmCNS.load((dict: jsonDict, path: json.path))
|
||||
vCLog("lmCNS: \(Self.lmCNS.count) entries of data loaded from: \(json.path)")
|
||||
}
|
||||
|
||||
public var isMiscDataLoaded: Bool { lmMisc.isLoaded }
|
||||
public func loadMiscData(json: (dict: [String: [String]]?, path: String)) {
|
||||
guard let jsonDict = json.dict else {
|
||||
vCLog("lmCore: File access failure: \(json.path)")
|
||||
return
|
||||
}
|
||||
lmMisc.load((dict: jsonDict, path: json.path))
|
||||
vCLog("lmMisc: \(lmMisc.count) entries of data loaded from: \(json.path)")
|
||||
}
|
||||
|
||||
public var isSymbolDataLoaded: Bool { Self.lmSymbols.isLoaded }
|
||||
public func loadSymbolData(json: (dict: [String: [String]]?, path: String)) {
|
||||
guard let jsonDict = json.dict else {
|
||||
vCLog("lmCore: File access failure: \(json.path)")
|
||||
return
|
||||
}
|
||||
Self.lmSymbols.load((dict: jsonDict, path: json.path))
|
||||
vCLog("lmSymbols: \(Self.lmSymbols.count) entries of data loaded from: \(json.path)")
|
||||
}
|
||||
|
||||
// 上述幾個函式不要加 Async,因為這些內容都被 LMMgr 負責用別的方法 Async 了、用 GCD 的多任務並行共結來完成。
|
||||
public func resetFactoryJSONModels() {}
|
||||
|
||||
public func loadUserPhrasesData(path: String, filterPath: String?) {
|
||||
DispatchQueue.main.async {
|
||||
|
@ -210,7 +175,12 @@ public extension vChewingLM {
|
|||
}
|
||||
}
|
||||
|
||||
public func loadSCPCSequencesData(path: String) {
|
||||
public func loadSCPCSequencesData() {
|
||||
let fileName = !isCHS ? "sequenceDataFromEtenDOS-cht" : "sequenceDataFromEtenDOS-chs"
|
||||
guard let path = Bundle.module.path(forResource: fileName, ofType: "json") else {
|
||||
vCLog("lmPlainBopomofo: File name access failure: \(fileName)")
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
if FileManager.default.isReadableFile(atPath: path) {
|
||||
self.lmPlainBopomofo.clear()
|
||||
|
@ -322,7 +292,7 @@ public extension vChewingLM {
|
|||
/// - Returns: 是否在庫。
|
||||
public func hasKeyValuePairFor(keyArray: [String], value: String, factoryDictionaryOnly: Bool = false) -> Bool {
|
||||
factoryDictionaryOnly
|
||||
? lmCore.unigramsFor(key: keyArray.joined(separator: "-")).map(\.value).contains(value)
|
||||
? factoryCoreUnigramsFor(key: keyArray.joined(separator: "-")).map(\.value).contains(value)
|
||||
: unigramsFor(keyArray: keyArray).map(\.value).contains(value)
|
||||
}
|
||||
|
||||
|
@ -333,7 +303,7 @@ public extension vChewingLM {
|
|||
/// - Returns: 是否在庫。
|
||||
public func countKeyValuePairs(keyArray: [String], factoryDictionaryOnly: Bool = false) -> Int {
|
||||
factoryDictionaryOnly
|
||||
? lmCore.unigramsFor(key: keyArray.joined(separator: "-")).count
|
||||
? factoryCoreUnigramsFor(key: keyArray.joined(separator: "-")).count
|
||||
: unigramsFor(keyArray: keyArray).count
|
||||
}
|
||||
|
||||
|
@ -363,15 +333,17 @@ public extension vChewingLM {
|
|||
|
||||
if !isCassetteEnabled || isCassetteEnabled && keyChain.map(\.description)[0] == "_" {
|
||||
// LMMisc 與 LMCore 的 score 在 (-10.0, 0.0) 這個區間內。
|
||||
rawAllUnigrams += lmMisc.unigramsFor(key: keyChain)
|
||||
rawAllUnigrams += lmCore.unigramsFor(key: keyChain)
|
||||
if isCNSEnabled { rawAllUnigrams += Self.lmCNS.unigramsFor(key: keyChain) }
|
||||
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCHEW)
|
||||
rawAllUnigrams += factoryCoreUnigramsFor(key: keyChain)
|
||||
if isCNSEnabled {
|
||||
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataCNS)
|
||||
}
|
||||
}
|
||||
|
||||
if isSymbolEnabled {
|
||||
rawAllUnigrams += lmUserSymbols.unigramsFor(key: keyChain)
|
||||
if !isCassetteEnabled {
|
||||
rawAllUnigrams += Self.lmSymbols.unigramsFor(key: keyChain)
|
||||
rawAllUnigrams += factoryUnigramsFor(key: keyChain, column: .theDataSYMB)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,7 +351,7 @@ public extension vChewingLM {
|
|||
rawAllUnigrams.append(contentsOf: queryDateTimeUnigrams(with: keyChain))
|
||||
|
||||
if keyChain == "_punctuation_list" {
|
||||
rawAllUnigrams.append(contentsOf: lmCore.getHaninSymbolMenuUnigrams())
|
||||
rawAllUnigrams.append(contentsOf: getHaninSymbolMenuUnigrams())
|
||||
}
|
||||
|
||||
// 提前處理語彙置換
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
// No trademark license is granted to use the trade names, trademarks, service
|
||||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
import SQLite3
|
||||
|
||||
/* ==============
|
||||
因應 Apple 對 8GB 運行記憶體的病態偏執,威注音的原廠辭典格式更換為 SQLite、以圖減少對記憶體的佔用。
|
||||
資料結構如下:
|
||||
CREATE TABLE IF NOT EXISTS DATA_MAIN (
|
||||
theKey TEXT NOT NULL,
|
||||
theDataCHS TEXT,
|
||||
theDataCHT TEXT,
|
||||
theDataCNS TEXT,
|
||||
theDataMISC TEXT,
|
||||
theDataSYMB TEXT,
|
||||
theDataCHEW TEXT,
|
||||
PRIMARY KEY (theKey)
|
||||
) WITHOUT ROWID;
|
||||
CREATE TABLE IF NOT EXISTS DATA_REV (
|
||||
theChar TEXT NOT NULL,
|
||||
theReadings TEXT NOT NULL,
|
||||
PRIMARY KEY (theChar)
|
||||
) WITHOUT ROWID;
|
||||
*/
|
||||
|
||||
enum CoreColumn: Int32 {
|
||||
case theDataCHS = 1 // 簡體中文
|
||||
case theDataCHT = 2 // 繁體中文
|
||||
case theDataCNS = 3 // 全字庫
|
||||
case theDataMISC = 4 // 待辦
|
||||
case theDataSYMB = 5 // 符號圖
|
||||
case theDataCHEW = 6 // 注音文
|
||||
|
||||
var name: String { String(describing: self) }
|
||||
|
||||
var id: Int32 { rawValue }
|
||||
|
||||
var defaultScore: Double {
|
||||
switch self {
|
||||
case .theDataCHEW: return -1
|
||||
case .theDataCNS: return -11
|
||||
case .theDataSYMB: return -13
|
||||
case .theDataMISC: return -10
|
||||
default: return -9.9
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension vChewingLM.LMInstantiator {
|
||||
fileprivate static func querySQL(strStmt sqlQuery: String, coreColumn column: CoreColumn, handler: (String) -> Void) {
|
||||
guard Self.ptrSQL != nil else { return }
|
||||
defer { sqlite3_finalize(Self.ptrStatement) }
|
||||
sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &Self.ptrStatement, nil)
|
||||
while sqlite3_step(Self.ptrStatement) == SQLITE_ROW {
|
||||
guard let rawValue = sqlite3_column_text(Self.ptrStatement, column.id) else { continue }
|
||||
handler(String(cString: rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static func hasSQLResult(strStmt sqlQuery: String, coreColumn column: CoreColumn) -> Bool {
|
||||
guard Self.ptrSQL != nil else { return false }
|
||||
defer { sqlite3_finalize(Self.ptrStatement) }
|
||||
sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &Self.ptrStatement, nil)
|
||||
while sqlite3_step(Self.ptrStatement) == SQLITE_ROW {
|
||||
guard sqlite3_column_text(Self.ptrStatement, column.id) != nil else { continue }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/// 获取字根反查资料。
|
||||
public static func getFactoryReverseLookupData(with kanji: String) -> [String]? {
|
||||
var results: [String] = []
|
||||
let sqlQuery = "SELECT * FROM DATA_REV WHERE theChar='\(kanji)';"
|
||||
guard Self.ptrSQL != nil else { return nil }
|
||||
defer { sqlite3_finalize(Self.ptrStatement) }
|
||||
sqlite3_prepare_v2(Self.ptrSQL, sqlQuery, -1, &Self.ptrStatement, nil)
|
||||
while sqlite3_step(Self.ptrStatement) == SQLITE_ROW {
|
||||
guard let rawValue = sqlite3_column_text(Self.ptrStatement, 1) else { continue }
|
||||
results.append(
|
||||
contentsOf: String(cString: rawValue).split(separator: "\t").map { reading in
|
||||
Self.restorePhonabetFromASCII(reading.description)
|
||||
}
|
||||
)
|
||||
}
|
||||
return results.isEmpty ? nil : results
|
||||
}
|
||||
|
||||
func getHaninSymbolMenuUnigrams() -> [Megrez.Unigram] {
|
||||
let column: CoreColumn = isCHS ? .theDataCHS : .theDataCHT
|
||||
var grams: [Megrez.Unigram] = []
|
||||
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='_punctuation_list';"
|
||||
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
|
||||
let arrRangeRecords = currentResult.split(separator: "\t")
|
||||
for strNetaSet in arrRangeRecords {
|
||||
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
|
||||
let theValue: String = .init(neta[0])
|
||||
var theScore = column.defaultScore
|
||||
if neta.count >= 2, let thisScore = Double(String(neta[1])) {
|
||||
theScore = thisScore
|
||||
}
|
||||
if theScore > 0 {
|
||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||
}
|
||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||
}
|
||||
}
|
||||
return grams
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取原廠標準資料庫辭典內的對應資料陣列的 UTF8 資料、就地分析、生成單元圖陣列。
|
||||
/// - parameters:
|
||||
/// - key: 讀音索引鍵。
|
||||
func factoryCoreUnigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
// 此處需要把 ASCII 單引號換成連續兩個單引號,否則會有 SQLite 語句查詢故障。
|
||||
factoryUnigramsFor(key: key, column: isCHS ? .theDataCHS : .theDataCHT)
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取原廠標準資料庫辭典內的對應資料陣列的 UTF8 資料、就地分析、生成單元圖陣列。
|
||||
/// - parameters:
|
||||
/// - key: 讀音索引鍵。
|
||||
/// - column: 資料欄位。
|
||||
func factoryUnigramsFor(key: String, column: CoreColumn) -> [Megrez.Unigram] {
|
||||
if key == "_punctuation_list" { return [] }
|
||||
var grams: [Megrez.Unigram] = []
|
||||
var gramsHW: [Megrez.Unigram] = []
|
||||
// 此處需要把 ASCII 單引號換成連續兩個單引號,否則會有 SQLite 語句查詢故障。
|
||||
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
|
||||
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
|
||||
Self.querySQL(strStmt: sqlQuery, coreColumn: column) { currentResult in
|
||||
let arrRangeRecords = currentResult.split(separator: "\t")
|
||||
for strNetaSet in arrRangeRecords {
|
||||
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
|
||||
let theValue: String = .init(neta[0])
|
||||
var theScore = column.defaultScore
|
||||
if neta.count >= 2, let thisScore = Double(String(neta[1])) {
|
||||
theScore = thisScore
|
||||
}
|
||||
if theScore > 0 {
|
||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||
}
|
||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||
if !key.contains("_punctuation") { continue }
|
||||
let halfValue = theValue.applyingTransformFW2HW(reverse: false)
|
||||
if halfValue != theValue {
|
||||
gramsHW.append(Megrez.Unigram(value: halfValue, score: theScore))
|
||||
}
|
||||
}
|
||||
}
|
||||
grams.append(contentsOf: gramsHW)
|
||||
return grams
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取原廠資料庫辭典內的對應資料陣列的 UTF8 資料、就地分析、生成單元圖陣列。
|
||||
/// - parameters:
|
||||
/// - key: 讀音索引鍵。
|
||||
func hasFactoryCoreUnigramsFor(key: String) -> Bool {
|
||||
let column: CoreColumn = isCHS ? .theDataCHS : .theDataCHT
|
||||
// 此處需要把 ASCII 單引號換成連續兩個單引號,否則會有 SQLite 語句查詢故障。
|
||||
let encryptedKey = Self.cnvPhonabetToASCII(key.replacingOccurrences(of: "'", with: "''"))
|
||||
let sqlQuery = "SELECT * FROM DATA_MAIN WHERE theKey='\(encryptedKey)';"
|
||||
return Self.hasSQLResult(strStmt: sqlQuery, coreColumn: column)
|
||||
}
|
||||
}
|
||||
|
||||
private extension vChewingLM.LMInstantiator {
|
||||
/// 內部函式,用以將注音讀音索引鍵進行加密。
|
||||
///
|
||||
/// 使用這種加密字串作為索引鍵,可以增加對 json 資料庫的存取速度。
|
||||
///
|
||||
/// 如果傳入的字串當中包含 ASCII 下畫線符號的話,則表明該字串並非注音讀音字串,會被忽略處理。
|
||||
/// - parameters:
|
||||
/// - incoming: 傳入的未加密注音讀音字串。
|
||||
static func cnvPhonabetToASCII(_ incoming: String) -> String {
|
||||
var strOutput = incoming
|
||||
if !strOutput.contains("_") {
|
||||
for entry in Self.dicPhonabet2ASCII {
|
||||
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
|
||||
}
|
||||
}
|
||||
return strOutput
|
||||
}
|
||||
|
||||
static let dicPhonabet2ASCII: [String: String] = [
|
||||
"ㄅ": "b", "ㄆ": "p", "ㄇ": "m", "ㄈ": "f", "ㄉ": "d", "ㄊ": "t", "ㄋ": "n", "ㄌ": "l", "ㄍ": "g", "ㄎ": "k", "ㄏ": "h",
|
||||
"ㄐ": "j", "ㄑ": "q", "ㄒ": "x", "ㄓ": "Z", "ㄔ": "C", "ㄕ": "S", "ㄖ": "r", "ㄗ": "z", "ㄘ": "c", "ㄙ": "s", "ㄧ": "i",
|
||||
"ㄨ": "u", "ㄩ": "v", "ㄚ": "a", "ㄛ": "o", "ㄜ": "e", "ㄝ": "E", "ㄞ": "B", "ㄟ": "P", "ㄠ": "M", "ㄡ": "F", "ㄢ": "D",
|
||||
"ㄣ": "T", "ㄤ": "N", "ㄥ": "L", "ㄦ": "R", "ˊ": "2", "ˇ": "3", "ˋ": "4", "˙": "5",
|
||||
]
|
||||
|
||||
/// 內部函式,用以將被加密的注音讀音索引鍵進行解密。
|
||||
///
|
||||
/// 如果傳入的字串當中包含 ASCII 下畫線符號的話,則表明該字串並非注音讀音字串,會被忽略處理。
|
||||
/// - parameters:
|
||||
/// - incoming: 傳入的已加密注音讀音字串。
|
||||
static func restorePhonabetFromASCII(_ incoming: String) -> String {
|
||||
var strOutput = incoming
|
||||
if !strOutput.contains("_") {
|
||||
for entry in Self.dicPhonabet4ASCII {
|
||||
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
|
||||
}
|
||||
}
|
||||
return strOutput
|
||||
}
|
||||
|
||||
static let dicPhonabet4ASCII: [String: String] = [
|
||||
"b": "ㄅ", "p": "ㄆ", "m": "ㄇ", "f": "ㄈ", "d": "ㄉ", "t": "ㄊ", "n": "ㄋ", "l": "ㄌ", "g": "ㄍ", "k": "ㄎ", "h": "ㄏ",
|
||||
"j": "ㄐ", "q": "ㄑ", "x": "ㄒ", "Z": "ㄓ", "C": "ㄔ", "S": "ㄕ", "r": "ㄖ", "z": "ㄗ", "c": "ㄘ", "s": "ㄙ", "i": "ㄧ",
|
||||
"u": "ㄨ", "v": "ㄩ", "a": "ㄚ", "o": "ㄛ", "e": "ㄜ", "E": "ㄝ", "B": "ㄞ", "P": "ㄟ", "M": "ㄠ", "F": "ㄡ", "D": "ㄢ",
|
||||
"T": "ㄣ", "N": "ㄤ", "L": "ㄥ", "R": "ㄦ", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
|
||||
]
|
||||
}
|
||||
|
||||
public extension vChewingLM.LMInstantiator {
|
||||
@discardableResult static func connectToTestSQLDB() -> Bool {
|
||||
Self.connectSQLDB(dbPath: #":memory:"#) && sqlTestCoreLMData.runAsSQLExec(dbPointer: &ptrSQL)
|
||||
}
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
// No trademark license is granted to use the trade names, trademarks, service
|
||||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Megrez
|
||||
import Shared
|
||||
|
||||
public extension vChewingLM {
|
||||
/// 與之前的 LMCore 不同,LMCoreJSON 直接讀取 json。
|
||||
/// 這樣一來可以節省在舊 mac 機種內的資料讀入速度。
|
||||
/// 目前僅針對輸入法原廠語彙資料檔案使用 json 格式。
|
||||
@frozen struct LMCoreJSON {
|
||||
public private(set) var filePath: String?
|
||||
/// 資料庫辭典。索引內容為經過加密的注音字串,資料內容則為 UTF8 資料陣列。
|
||||
var dataMap: [String: [String]] = [:]
|
||||
/// 【已作廢】資料庫字串陣列。在 LMCoreJSON 內沒有作用。
|
||||
var strData: String = ""
|
||||
/// 【已作廢】聲明原始檔案內第一、二縱列的內容是否彼此顛倒。
|
||||
var shouldReverse = false
|
||||
/// 請且僅請對使用者語言模組啟用該參數:是否自動整理格式。
|
||||
var allowConsolidation = false
|
||||
/// 當某一筆資料內的權重資料毀損時,要施加的預設權重。
|
||||
var defaultScore: Double = 0
|
||||
/// 啟用該選項的話,會強制施加預設權重、而無視原始權重資料。
|
||||
var shouldForceDefaultScore = false
|
||||
|
||||
/// 資料陣列內承載的資料筆數。
|
||||
public var count: Int { dataMap.count }
|
||||
|
||||
/// 初期化該語言模型。
|
||||
///
|
||||
/// 某些參數在 LMCoreJSON 內已作廢,但仍保留、以方便那些想用該專案源碼做實驗的人群。
|
||||
///
|
||||
/// - parameters:
|
||||
/// - reverse: 已作廢:聲明原始檔案內第一、二縱列的內容是否彼此顛倒。
|
||||
/// - consolidate: 請且僅請對使用者語言模組啟用該參數:是否自動整理格式。
|
||||
/// - defaultScore: 當某一筆資料內的權重資料毀損時,要施加的預設權重。
|
||||
/// - forceDefaultScore: 啟用該選項的話,會強制施加預設權重、而無視原始權重資料。
|
||||
public init(
|
||||
reverse: Bool = false, consolidate: Bool = false, defaultScore scoreDefault: Double = 0,
|
||||
forceDefaultScore: Bool = false
|
||||
) {
|
||||
dataMap = [:]
|
||||
allowConsolidation = consolidate
|
||||
shouldReverse = reverse
|
||||
defaultScore = scoreDefault
|
||||
shouldForceDefaultScore = forceDefaultScore
|
||||
}
|
||||
|
||||
/// 檢測資料庫辭典內是否已經有載入的資料。
|
||||
public var isLoaded: Bool { !dataMap.isEmpty }
|
||||
|
||||
/// 讀入資料辭典。
|
||||
/// - parameters:
|
||||
/// - dictData: 辭典資料及對應的 URL 位置。
|
||||
public mutating func load(_ dictData: (dict: [String: [String]], path: String)) {
|
||||
if isLoaded { return }
|
||||
filePath = dictData.path
|
||||
dataMap = dictData.dict
|
||||
}
|
||||
|
||||
/// 將資料從檔案讀入至資料庫辭典內。
|
||||
/// - parameters:
|
||||
/// - path: 給定路徑。
|
||||
@discardableResult public mutating func open(_ path: String) -> Bool {
|
||||
if isLoaded { return false }
|
||||
let oldPath = filePath
|
||||
filePath = nil
|
||||
|
||||
do {
|
||||
let rawData = try Data(contentsOf: URL(fileURLWithPath: path))
|
||||
if let rawJSON = try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]] {
|
||||
dataMap = rawJSON
|
||||
} else {
|
||||
filePath = oldPath
|
||||
vCLog("↑ Exception happened when reading JSON file at: \(path).")
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
filePath = oldPath
|
||||
vCLog("↑ Exception happened when reading JSON file at: \(path).")
|
||||
return false
|
||||
}
|
||||
|
||||
filePath = path
|
||||
return true
|
||||
}
|
||||
|
||||
/// 將當前語言模組的資料庫辭典自記憶體內卸除。
|
||||
public mutating func clear() {
|
||||
filePath = nil
|
||||
strData.removeAll()
|
||||
dataMap.removeAll()
|
||||
}
|
||||
|
||||
// MARK: - Advanced features
|
||||
|
||||
public func saveData() {
|
||||
guard let filePath = filePath, let jsonURL = URL(string: filePath) else { return }
|
||||
do {
|
||||
try JSONSerialization.data(withJSONObject: dataMap, options: .sortedKeys).write(to: jsonURL)
|
||||
} catch {
|
||||
vCLog("Failed to save current database to: \(filePath)")
|
||||
}
|
||||
}
|
||||
|
||||
/// 將當前資料庫辭典的內容以文本的形式輸出至 macOS 內建的 Console.app。
|
||||
///
|
||||
/// 該功能僅作偵錯之用途。
|
||||
public func dump() {
|
||||
var strDump = ""
|
||||
for entry in dataMap {
|
||||
let netaSets: [String] = entry.value
|
||||
let theKey = entry.key
|
||||
for strNetaSet in netaSets {
|
||||
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).components(separatedBy: " ").reversed())
|
||||
let theValue = neta[0]
|
||||
var theScore = defaultScore
|
||||
if neta.count >= 2, !shouldForceDefaultScore {
|
||||
theScore = .init(String(neta[1])) ?? defaultScore
|
||||
}
|
||||
strDump += "\(Self.cnvPhonabetToASCII(theKey)) \(theValue) \(theScore)\n"
|
||||
}
|
||||
}
|
||||
vCLog(strDump)
|
||||
}
|
||||
|
||||
public func getHaninSymbolMenuUnigrams() -> [Megrez.Unigram] {
|
||||
let key = "_punctuation_list"
|
||||
var grams: [Megrez.Unigram] = []
|
||||
guard let arrRangeRecords: [String] = dataMap[Self.cnvPhonabetToASCII(key)] else { return grams }
|
||||
for strNetaSet in arrRangeRecords {
|
||||
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
|
||||
let theValue: String = .init(neta[0])
|
||||
var theScore = defaultScore
|
||||
if neta.count >= 2, !shouldForceDefaultScore {
|
||||
theScore = .init(String(neta[1])) ?? defaultScore
|
||||
}
|
||||
if theScore > 0 {
|
||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||
}
|
||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||
}
|
||||
return grams
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵,來獲取資料庫辭典內的對應資料陣列的 UTF8 資料、就地分析、生成單元圖陣列。
|
||||
/// - parameters:
|
||||
/// - key: 讀音索引鍵。
|
||||
public func unigramsFor(key: String) -> [Megrez.Unigram] {
|
||||
if key == "_punctuation_list" { return [] }
|
||||
var grams: [Megrez.Unigram] = []
|
||||
var gramsHW: [Megrez.Unigram] = []
|
||||
guard let arrRangeRecords: [String] = dataMap[Self.cnvPhonabetToASCII(key)] else { return grams }
|
||||
for strNetaSet in arrRangeRecords {
|
||||
let neta = Array(strNetaSet.trimmingCharacters(in: .newlines).split(separator: " ").reversed())
|
||||
let theValue: String = .init(neta[0])
|
||||
var theScore = defaultScore
|
||||
if neta.count >= 2, !shouldForceDefaultScore {
|
||||
theScore = .init(String(neta[1])) ?? defaultScore
|
||||
}
|
||||
if theScore > 0 {
|
||||
theScore *= -1 // 應對可能忘記寫負號的情形
|
||||
}
|
||||
grams.append(Megrez.Unigram(value: theValue, score: theScore))
|
||||
if !key.contains("_punctuation") { continue }
|
||||
let halfValue = theValue.applyingTransformFW2HW(reverse: false)
|
||||
if halfValue != theValue {
|
||||
gramsHW.append(Megrez.Unigram(value: halfValue, score: theScore))
|
||||
}
|
||||
}
|
||||
grams.append(contentsOf: gramsHW)
|
||||
return grams
|
||||
}
|
||||
|
||||
/// 根據給定的讀音索引鍵來確認資料庫辭典內是否存在對應的資料。
|
||||
/// - parameters:
|
||||
/// - key: 讀音索引鍵。
|
||||
public func hasUnigramsFor(key: String) -> Bool {
|
||||
dataMap[Self.cnvPhonabetToASCII(key)] != nil
|
||||
}
|
||||
|
||||
/// 內部函式,用以將注音讀音索引鍵進行加密。
|
||||
///
|
||||
/// 使用這種加密字串作為索引鍵,可以增加對 json 資料庫的存取速度。
|
||||
///
|
||||
/// 如果傳入的字串當中包含 ASCII 下畫線符號的話,則表明該字串並非注音讀音字串,會被忽略處理。
|
||||
/// - parameters:
|
||||
/// - incoming: 傳入的未加密注音讀音字串。
|
||||
public static func cnvPhonabetToASCII(_ incoming: String) -> String {
|
||||
var strOutput = incoming
|
||||
if !strOutput.contains("_") {
|
||||
for entry in Self.dicPhonabet2ASCII {
|
||||
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
|
||||
}
|
||||
}
|
||||
return strOutput
|
||||
}
|
||||
|
||||
/// 內部函式,用以將被加密的注音讀音索引鍵進行解密。
|
||||
///
|
||||
/// 如果傳入的字串當中包含 ASCII 下畫線符號的話,則表明該字串並非注音讀音字串,會被忽略處理。
|
||||
/// - parameters:
|
||||
/// - incoming: 傳入的已加密注音讀音字串。
|
||||
public static func restorePhonabetFromASCII(_ incoming: String) -> String {
|
||||
var strOutput = incoming
|
||||
if !strOutput.contains("_") {
|
||||
for entry in Self.dicPhonabet4ASCII {
|
||||
strOutput = strOutput.replacingOccurrences(of: entry.key, with: entry.value)
|
||||
}
|
||||
}
|
||||
return strOutput
|
||||
}
|
||||
|
||||
// MARK: - Constants
|
||||
|
||||
static let dicPhonabet2ASCII: [String: String] = [
|
||||
"ㄅ": "b", "ㄆ": "p", "ㄇ": "m", "ㄈ": "f", "ㄉ": "d", "ㄊ": "t", "ㄋ": "n", "ㄌ": "l", "ㄍ": "g", "ㄎ": "k", "ㄏ": "h",
|
||||
"ㄐ": "j", "ㄑ": "q", "ㄒ": "x", "ㄓ": "Z", "ㄔ": "C", "ㄕ": "S", "ㄖ": "r", "ㄗ": "z", "ㄘ": "c", "ㄙ": "s", "ㄧ": "i",
|
||||
"ㄨ": "u", "ㄩ": "v", "ㄚ": "a", "ㄛ": "o", "ㄜ": "e", "ㄝ": "E", "ㄞ": "B", "ㄟ": "P", "ㄠ": "M", "ㄡ": "F", "ㄢ": "D",
|
||||
"ㄣ": "T", "ㄤ": "N", "ㄥ": "L", "ㄦ": "R", "ˊ": "2", "ˇ": "3", "ˋ": "4", "˙": "5",
|
||||
]
|
||||
|
||||
static let dicPhonabet4ASCII: [String: String] = [
|
||||
"b": "ㄅ", "p": "ㄆ", "m": "ㄇ", "f": "ㄈ", "d": "ㄉ", "t": "ㄊ", "n": "ㄋ", "l": "ㄌ", "g": "ㄍ", "k": "ㄎ", "h": "ㄏ",
|
||||
"j": "ㄐ", "q": "ㄑ", "x": "ㄒ", "Z": "ㄓ", "C": "ㄔ", "S": "ㄕ", "r": "ㄖ", "z": "ㄗ", "c": "ㄘ", "s": "ㄙ", "i": "ㄧ",
|
||||
"u": "ㄨ", "v": "ㄩ", "a": "ㄚ", "o": "ㄛ", "e": "ㄜ", "E": "ㄝ", "B": "ㄞ", "P": "ㄟ", "M": "ㄠ", "F": "ㄡ", "D": "ㄢ",
|
||||
"T": "ㄣ", "N": "ㄤ", "L": "ㄥ", "R": "ㄦ", "2": "ˊ", "3": "ˇ", "4": "ˋ", "5": "˙",
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// File.swift
|
||||
//
|
||||
//
|
||||
// Created by ShikiSuen on 2023/11/26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
let sqlTestCoreLMData: String = """
|
||||
PRAGMA foreign_keys=OFF;
|
||||
BEGIN TRANSACTION;
|
||||
CREATE TABLE DATA_MAIN (
|
||||
theKey TEXT NOT NULL,
|
||||
theDataCHS TEXT,
|
||||
theDataCHT TEXT,
|
||||
theDataCNS TEXT,
|
||||
theDataMISC TEXT,
|
||||
theDataSYMB TEXT,
|
||||
theDataCHEW TEXT,
|
||||
PRIMARY KEY (theKey)
|
||||
) WITHOUT ROWID;
|
||||
INSERT INTO DATA_MAIN VALUES('CuP-niF2-bi','-7.375 吹牛逼\t-7.399 吹牛屄','-7.375 吹牛逼\t-7.399 吹牛屄','','','🌳🆕🐝','');
|
||||
INSERT INTO DATA_MAIN VALUES('Ze4','-6.0 這','-6.0 這',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('Ze4-iN4','-6.0 這樣','-6.0 這樣',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ZuL','-5.809297 中\t-99.0 終\t-9.87758 鐘\t-9.685671 鍾\t-99.0 盅\t-99.0 忠','-5.809297 中\t-99.0 終\t-9.87758 鐘\t-9.685671 鍾\t-99.0 盅\t-99.0 忠',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('_punctuation_list',' \t,\t、\t。',' \t,\t、\t。','','','','');
|
||||
INSERT INTO DATA_MAIN VALUES('de5','-3.516024 的\t-7.427179 得','-3.516024 的\t-7.427179 得',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('di2','-3.516024 的','-3.516024 的',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('di4','-3.516024 的','-3.516024 的',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('duP3','-9.544 㨃','-9.544 㨃','㨃\t䇏\t𦞙\t謉\t𠡒\t𡑈\t𥫉\t𦞱\t𧫏\t𩛔','','','');
|
||||
INSERT INTO DATA_MAIN VALUES('fL','-11.0 🐝','-11.0 🐝',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('gM','-7.171551 高\t-11.92872 膏\t-13.624335 篙\t-12.390804 糕','-7.171551 高\t-11.92872 膏\t-13.624335 篙\t-12.390804 糕',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('gM-ke-ji4','-9.842421 高科技','-9.842421 高科技',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('guL','-8.381971 共\t-8.501463 供\t-8.858181 紅\t-7.877973 公\t-7.822167 工\t-99.0 攻\t-99.0 功\t-99.0 宮\t-99.0 弓\t-99.0 恭\t-99.0 躬','-8.381971 共\t-8.501463 供\t-8.858181 紅\t-7.877973 公\t-7.822167 工\t-99.0 攻\t-99.0 功\t-99.0 宮\t-99.0 弓\t-99.0 恭\t-99.0 躬',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('guL-s','-6.299461 公司','-6.299461 公司',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('iN4','-6.0 樣','-6.0 樣',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ji4','-99.0 既\t-7.608341 際\t-99.0 季\t-10.939895 騎\t-99.0 記\t-99.0 寄\t-9.715317 繼\t-7.926683 計\t-8.373022 暨\t-10.425662 繫\t-8.888722 劑\t-10.204425 祭\t-99.0 忌\t-8.450826 技\t-12.045357 冀\t-99.0 妓\t-9.517568 濟\t-12.021587 薊','-99.0 既\t-7.608341 際\t-99.0 季\t-10.939895 騎\t-99.0 記\t-99.0 寄\t-9.715317 繼\t-7.926683 計\t-8.373022 暨\t-10.425662 繫\t-8.888722 劑\t-10.204425 祭\t-99.0 忌\t-8.450826 技\t-12.045357 冀\t-99.0 妓\t-9.517568 濟\t-12.021587 薊',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ji4-guL','-13.336653 濟公','-13.336653 濟公',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('jiM4','-3.676169 教\t-3.24869962 較','-3.676169 教\t-3.24869962 較',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('jiM4-v4','-3.32220565 教育','-3.32220565 教育',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('jiN3','-9.164384 講\t-8.690941 獎\t-10.127828 蔣\t-12.492933 槳','-9.164384 講\t-8.690941 獎\t-10.127828 蔣\t-12.492933 槳',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('jiN3-jiT','-10.344678 獎金','-10.344678 獎金',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('jiT','-8.034095 今\t-7.290109 金\t-99.0 斤\t-10.711079 禁\t-11.378321 浸\t-11.07489 筋\t-99.0 巾\t-12.784206 襟','-8.034095 今\t-7.290109 金\t-99.0 斤\t-10.711079 禁\t-11.378321 浸\t-11.07489 筋\t-99.0 巾\t-12.784206 襟',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ke','-10.574273 顆\t-11.504072 棵\t-10.450457 刻\t-7.171052 科\t-99.0 柯','-10.574273 顆\t-11.504072 棵\t-10.450457 刻\t-7.171052 科\t-99.0 柯',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ke-ji4','-6.736613 科技','-6.736613 科技',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ke-ke','-8.0 顆顆','-8.0 顆顆',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('mi4','-4.6231 蜜','-4.6231 蜜',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('mi4-fL','-11.0 🐝','-11.0 🐝',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('nP-nP','','','','','☉☉','ㄋㄟㄋㄟ');
|
||||
INSERT INTO DATA_MAIN VALUES('ni3','-6.0 你','-6.0 你',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('ni3-Ze4','-9.0 你這','-9.0 你這',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('niD2','-6.086515 年\t-11.336864 黏\t-11.28574 粘','-6.086515 年\t-11.336864 黏\t-11.28574 粘',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('niD2-ZuL','-11.668947 年終\t-11.373044 年中','-11.668947 年終\t-11.373044 年中',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('s','-9.495858 絲\t-9.006414 思\t-99.0 私\t-8.091803 斯\t-99.0 司\t-13.513987 嘶\t-12.259095 撕','-9.495858 絲\t-9.006414 思\t-99.0 私\t-8.091803 斯\t-99.0 司\t-13.513987 嘶\t-12.259095 撕',NULL,NULL,NULL,NULL);
|
||||
INSERT INTO DATA_MAIN VALUES('v4','-3.30192952 育','-3.30192952 育',NULL,NULL,NULL,NULL);
|
||||
CREATE TABLE DATA_REV (
|
||||
theChar TEXT NOT NULL,
|
||||
theReadings TEXT NOT NULL,
|
||||
PRIMARY KEY (theChar)
|
||||
) WITHOUT ROWID;
|
||||
INSERT INTO DATA_REV VALUES('和','huo2\the5\thuo\tduL\the2\the4\thD4\thu2\thuo5\thuo4');
|
||||
COMMIT;
|
||||
"""
|
|
@ -7,6 +7,8 @@
|
|||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import Shared
|
||||
import SQLite3
|
||||
|
||||
public enum vChewingLM {
|
||||
enum FileErrors: Error {
|
||||
|
@ -15,6 +17,7 @@ public enum vChewingLM {
|
|||
|
||||
public enum ReplacableUserDataType: String, CaseIterable, Identifiable {
|
||||
public var id: ObjectIdentifier { .init(rawValue as AnyObject) }
|
||||
public var localizedDescription: String { NSLocalizedString(rawValue, comment: "") }
|
||||
|
||||
case thePhrases
|
||||
case theFilter
|
||||
|
@ -23,3 +26,37 @@ public enum vChewingLM {
|
|||
case theSymbols
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - String as SQL Command
|
||||
|
||||
extension String {
|
||||
@discardableResult func runAsSQLExec(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
|
||||
ptrDB != nil && sqlite3_exec(ptrDB, self, nil, nil, nil) == SQLITE_OK
|
||||
}
|
||||
|
||||
@discardableResult func runAsSQLPreparedStep(dbPointer ptrDB: inout OpaquePointer?, stmtPtr ptrStmt: inout OpaquePointer?) -> Bool {
|
||||
guard ptrDB != nil else { return false }
|
||||
return sqlite3_prepare_v2(ptrDB, self, -1, &ptrStmt, nil) == SQLITE_OK && sqlite3_step(ptrStmt) == SQLITE_DONE
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == String {
|
||||
@discardableResult func runAsSQLPreparedSteps(dbPointer ptrDB: inout OpaquePointer?) -> Bool {
|
||||
guard ptrDB != nil else { return false }
|
||||
guard "begin;".runAsSQLExec(dbPointer: &ptrDB) else { return false }
|
||||
defer {
|
||||
let looseEnds = sqlite3_exec(ptrDB, "commit;", nil, nil, nil) == SQLITE_OK
|
||||
assert(looseEnds)
|
||||
}
|
||||
|
||||
var ptrStmt: OpaquePointer?
|
||||
defer { sqlite3_finalize(ptrStmt) }
|
||||
for strStmt in self {
|
||||
guard sqlite3_prepare_v2(ptrDB, strStmt, -1, &ptrStmt, nil) == SQLITE_OK, sqlite3_step(ptrStmt) == SQLITE_DONE else {
|
||||
vCLog("SQL Query Error. Statement: \(strStmt)")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
//// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
// No trademark license is granted to use the trade names, trademarks, service
|
||||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
|
||||
@testable import LangModelAssembly
|
||||
|
||||
private let strBloatingKey: [String] = ["ㄔㄨㄟ", "ㄋㄧㄡˊ", "ㄅㄧ"]
|
||||
private let strHaninSymbolMenuKey: [String] = ["_punctuation_list"]
|
||||
private let strRefutationKey: [String] = ["ㄉㄨㄟˇ"]
|
||||
private let strBoobsKey: [String] = ["ㄋㄟ", "ㄋㄟ"]
|
||||
private let expectedReverseLookupResults: [String] = [
|
||||
"ㄏㄨㄛˊ", "ㄏㄜ˙", "ㄏㄨㄛ", "ㄉㄨㄥ", "ㄏㄜˊ",
|
||||
"ㄏㄜˋ", "ㄏㄢˋ", "ㄏㄨˊ", "ㄏㄨㄛ˙", "ㄏㄨㄛˋ",
|
||||
]
|
||||
|
||||
final class LMInstantiatorSQLTests: XCTestCase {
|
||||
func testSQL() throws {
|
||||
let instance = vChewingLM.LMInstantiator(isCHS: true)
|
||||
XCTAssertTrue(vChewingLM.LMInstantiator.connectToTestSQLDB())
|
||||
instance.isCNSEnabled = false
|
||||
instance.isSymbolEnabled = false
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).description, "[(吹牛逼,-7.375), (吹牛屄,-7.399)]")
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,,-9.9)")
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).description, "[(㨃,-9.544)]")
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).description, "[(ㄋㄟㄋㄟ,-1.0)]")
|
||||
instance.isCNSEnabled = true
|
||||
instance.isSymbolEnabled = true
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strBloatingKey).last?.description, "(🌳🆕🐝,-13.0)")
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strHaninSymbolMenuKey)[1].description, "(,,-9.9)")
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strRefutationKey).count, 10)
|
||||
XCTAssertEqual(instance.unigramsFor(keyArray: strBoobsKey).last?.description, "(☉☉,-13.0)")
|
||||
// 再測試反查。
|
||||
XCTAssertEqual(vChewingLM.LMInstantiator.getFactoryReverseLookupData(with: "和"), expectedReverseLookupResults)
|
||||
vChewingLM.LMInstantiator.disconnectSQLDB()
|
||||
}
|
||||
}
|
|
@ -56,6 +56,9 @@ let package = Package(
|
|||
.product(name: "TooltipUI", package: "vChewing_TooltipUI"),
|
||||
.product(name: "Uninstaller", package: "vChewing_Uninstaller"),
|
||||
.product(name: "UpdateSputnik", package: "vChewing_UpdateSputnik"),
|
||||
],
|
||||
resources: [
|
||||
.process("Resources/convdict.sqlite"),
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
|
|
|
@ -87,7 +87,7 @@ public extension AppDelegate {
|
|||
)
|
||||
}
|
||||
|
||||
if !PrefMgr.shared.onlyLoadFactoryLangModelsIfNeeded { LMMgr.loadDataModelsOnAppDelegate() }
|
||||
LMMgr.connectCoreDB()
|
||||
LMMgr.loadCassetteData()
|
||||
LMMgr.initUserLangModels()
|
||||
folderMonitor.folderDidChange = { [weak self] in
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
import Hotenka
|
||||
|
||||
public enum ChineseConverter {
|
||||
public static let shared = HotenkaChineseConverter(jsonDir: LMMgr.getBundleDataPath("convdict", ext: "json"))
|
||||
public static let shared = HotenkaChineseConverter(
|
||||
sqliteDir: LMMgr.getBundleDataPath("convdict", ext: "sqlite") ?? ":memory:"
|
||||
)
|
||||
|
||||
private static var punctuationConversionTable: [(String, String)] = [
|
||||
("【", "︻"), ("】", "︼"), ("〖", "︗"), ("〗", "︘"), ("〔", "︹"), ("〕", "︺"), ("《", "︽"), ("》", "︾"),
|
||||
|
|
|
@ -62,132 +62,18 @@ public class LMMgr {
|
|||
Self.loadUserPhrasesData()
|
||||
}
|
||||
|
||||
public static func loadCoreLanguageModelFile(
|
||||
filenameSansExtension: String, langModel lm: vChewingLM.LMInstantiator
|
||||
) {
|
||||
lm.loadLanguageModel(json: Self.getDictionaryData(filenameSansExtension))
|
||||
}
|
||||
public static var isCoreDBConnected: Bool { vChewingLM.LMInstantiator.isSQLDBConnected }
|
||||
|
||||
public static func loadDataModelsOnAppDelegate() {
|
||||
let globalQueue = DispatchQueue(label: "vChewingLM", qos: .unspecified, attributes: .concurrent)
|
||||
var showFinishNotification = false
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
if !Self.lmCHT.isCNSDataLoaded {
|
||||
Self.lmCHT.loadCNSData(json: Self.getDictionaryData("data-cns"))
|
||||
}
|
||||
if !Self.lmCHT.isMiscDataLoaded {
|
||||
Self.lmCHT.loadMiscData(json: Self.getDictionaryData("data-zhuyinwen"))
|
||||
}
|
||||
if !Self.lmCHT.isSymbolDataLoaded {
|
||||
Self.lmCHT.loadSymbolData(json: Self.getDictionaryData("data-symbols"))
|
||||
}
|
||||
if !Self.lmCHS.isCNSDataLoaded {
|
||||
Self.lmCHS.loadCNSData(json: Self.getDictionaryData("data-cns"))
|
||||
}
|
||||
if !Self.lmCHS.isMiscDataLoaded {
|
||||
Self.lmCHS.loadMiscData(json: Self.getDictionaryData("data-zhuyinwen"))
|
||||
}
|
||||
if !Self.lmCHS.isSymbolDataLoaded {
|
||||
Self.lmCHS.loadSymbolData(json: Self.getDictionaryData("data-symbols"))
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
if !Self.lmCHT.isCoreLMLoaded {
|
||||
showFinishNotification = true
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHT Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
loadCoreLanguageModelFile(filenameSansExtension: "data-cht", langModel: Self.lmCHT)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
if !Self.lmCHS.isCoreLMLoaded {
|
||||
showFinishNotification = true
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHS Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
loadCoreLanguageModelFile(filenameSansExtension: "data-chs", langModel: Self.lmCHS)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if showFinishNotification {
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func loadDataModel(_ mode: Shared.InputMode) {
|
||||
let globalQueue = DispatchQueue(label: "vChewingLM_Lazy", qos: .unspecified, attributes: .concurrent)
|
||||
var showFinishNotification = false
|
||||
let group = DispatchGroup()
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
let lm = Self.getLM(mode: mode)
|
||||
if !lm.isCNSDataLoaded {
|
||||
lm.loadCNSData(json: Self.getDictionaryData("data-cns"))
|
||||
}
|
||||
if !lm.isMiscDataLoaded {
|
||||
lm.loadMiscData(json: Self.getDictionaryData("data-zhuyinwen"))
|
||||
}
|
||||
if !lm.isSymbolDataLoaded {
|
||||
lm.loadSymbolData(json: Self.getDictionaryData("data-symbols"))
|
||||
}
|
||||
group.leave()
|
||||
}
|
||||
switch mode {
|
||||
case .imeModeCHS:
|
||||
if !Self.lmCHS.isCoreLMLoaded {
|
||||
showFinishNotification = true
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHS Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
loadCoreLanguageModelFile(filenameSansExtension: "data-chs", langModel: Self.lmCHS)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
case .imeModeCHT:
|
||||
if !Self.lmCHT.isCoreLMLoaded {
|
||||
showFinishNotification = true
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHT Core Dict...", comment: "")
|
||||
)
|
||||
group.enter()
|
||||
globalQueue.async {
|
||||
loadCoreLanguageModelFile(filenameSansExtension: "data-cht", langModel: Self.lmCHT)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
default: break
|
||||
}
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
if showFinishNotification {
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func reloadFactoryDictionaryFiles() {
|
||||
Broadcaster.shared.eventForReloadingRevLookupData = .init()
|
||||
LMMgr.lmCHS.resetFactoryJSONModels()
|
||||
LMMgr.lmCHT.resetFactoryJSONModels()
|
||||
if PrefMgr.shared.onlyLoadFactoryLangModelsIfNeeded {
|
||||
LMMgr.loadDataModel(IMEApp.currentInputMode)
|
||||
} else {
|
||||
LMMgr.loadDataModelsOnAppDelegate()
|
||||
public static func connectCoreDB(dbPath: String? = nil) {
|
||||
guard let path: String = dbPath ?? Self.getCoreDictionaryDBPath() else {
|
||||
assertionFailure("vChewing factory SQLite data not found.")
|
||||
return
|
||||
}
|
||||
let result = vChewingLM.LMInstantiator.connectSQLDB(dbPath: path)
|
||||
assert(result, "vChewing factory SQLite connection failed.")
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
|
||||
/// 載入磁帶資料。
|
||||
|
@ -269,12 +155,8 @@ public class LMMgr {
|
|||
}
|
||||
|
||||
public static func loadSCPCSequencesData() {
|
||||
Self.lmCHT.loadSCPCSequencesData(
|
||||
path: Self.etenSCPCSequencesURL(.imeModeCHT).path
|
||||
)
|
||||
Self.lmCHS.loadSCPCSequencesData(
|
||||
path: Self.etenSCPCSequencesURL(.imeModeCHS).path
|
||||
)
|
||||
Self.lmCHT.loadSCPCSequencesData()
|
||||
Self.lmCHS.loadSCPCSequencesData()
|
||||
}
|
||||
|
||||
public static func reloadUserFilterDirectly(mode: Shared.InputMode) {
|
||||
|
|
|
@ -65,33 +65,6 @@ extension LMMgr: PhraseEditorDelegate {
|
|||
|
||||
public func tagOverrides(in strProcessed: inout String, mode: Shared.InputMode) {
|
||||
let outputStack: NSMutableString = .init()
|
||||
switch mode {
|
||||
case .imeModeCHT:
|
||||
if !Self.lmCHT.isCoreLMLoaded {
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHT Core Dict...", comment: "")
|
||||
)
|
||||
Self.loadCoreLanguageModelFile(
|
||||
filenameSansExtension: "data-cht", langModel: Self.lmCHT
|
||||
)
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
case .imeModeCHS:
|
||||
if !Self.lmCHS.isCoreLMLoaded {
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Loading CHS Core Dict...", comment: "")
|
||||
)
|
||||
Self.loadCoreLanguageModelFile(
|
||||
filenameSansExtension: "data-chs", langModel: Self.lmCHS
|
||||
)
|
||||
Notifier.notify(
|
||||
message: NSLocalizedString("Core Dict loading complete.", comment: "")
|
||||
)
|
||||
}
|
||||
case .imeModeNULL: return
|
||||
}
|
||||
for currentLine in strProcessed.split(separator: "\n") {
|
||||
let arr = currentLine.split(separator: " ")
|
||||
guard arr.count >= 2 else { continue }
|
||||
|
|
|
@ -25,9 +25,10 @@ public extension LMMgr {
|
|||
|
||||
// 該函式目前僅供步天歌繁簡轉換引擎使用,並不會檢查目標檔案格式的實際可用性。
|
||||
|
||||
static func getBundleDataPath(_ filenameSansExt: String, factory: Bool = false, ext: String) -> String {
|
||||
static func getBundleDataPath(_ filenameSansExt: String, factory: Bool = false, ext: String) -> String? {
|
||||
let factoryPath = Bundle.main.path(forResource: filenameSansExt, ofType: ext) ?? Bundle.module.path(forResource: filenameSansExt, ofType: ext)
|
||||
guard let factoryPath = factoryPath else { return nil }
|
||||
let factory = PrefMgr.shared.useExternalFactoryDict ? factory : true
|
||||
let factoryPath = Bundle.main.path(forResource: filenameSansExt, ofType: ext)!
|
||||
let containerPath = Self.appSupportURL.appendingPathComponent("vChewingFactoryData/\(filenameSansExt).\(ext)").path
|
||||
.expandingTildeInPath
|
||||
var isFailed = false
|
||||
|
@ -40,46 +41,15 @@ public extension LMMgr {
|
|||
return result
|
||||
}
|
||||
|
||||
// MARK: - 獲取原廠核心語彙檔案資料本身(優先獲取 Containers 下的資料檔案),可能會出 nil。
|
||||
// MARK: - 獲取原廠核心語彙檔案(SQLite)的路徑(優先獲取 Containers 下的資料檔案)。
|
||||
|
||||
static func getDictionaryData(_ filenameSansExt: String, factory: Bool = false) -> (
|
||||
dict: [String: [String]]?, path: String
|
||||
) {
|
||||
let factory = PrefMgr.shared.useExternalFactoryDict ? factory : true
|
||||
let factoryResultURL = Bundle.main.url(forResource: filenameSansExt, withExtension: "json")
|
||||
let containerResultURL = Self.appSupportURL.appendingPathComponent("vChewingFactoryData/\(filenameSansExt).json")
|
||||
var lastReadPath = factoryResultURL?.path ?? "Factory file missing: \(filenameSansExt).json"
|
||||
|
||||
func getJSONData(url: URL?) -> [String: [String]]? {
|
||||
var isFailed = false
|
||||
var isFolder = ObjCBool(false)
|
||||
guard let url = url else {
|
||||
vCLog("URL Invalid.")
|
||||
return nil
|
||||
}
|
||||
defer { lastReadPath = url.path }
|
||||
if !FileManager.default.fileExists(atPath: url.path, isDirectory: &isFolder) { isFailed = true }
|
||||
if !isFailed, !FileManager.default.isReadableFile(atPath: url.path) { isFailed = true }
|
||||
if isFailed {
|
||||
vCLog("↑ Exception happened when reading json file at: \(url.path).")
|
||||
return nil
|
||||
}
|
||||
do {
|
||||
let rawData = try Data(contentsOf: url)
|
||||
return try? JSONSerialization.jsonObject(with: rawData) as? [String: [String]]
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
static func getCoreDictionaryDBPath(factory: Bool = false) -> String? {
|
||||
guard let factoryResultURL = Bundle.main.url(forResource: "vChewingFactoryDatabase", withExtension: "sqlite") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let result =
|
||||
factory
|
||||
? getJSONData(url: factoryResultURL)
|
||||
: getJSONData(url: containerResultURL) ?? getJSONData(url: factoryResultURL)
|
||||
if result == nil {
|
||||
vCLog("↑ Exception happened when reading json file at: \(lastReadPath).")
|
||||
}
|
||||
return (dict: result, path: lastReadPath)
|
||||
guard !factory, !PrefMgr.shared.useExternalFactoryDict else { return factoryResultURL.path }
|
||||
let containerResultURL = Self.appSupportURL.appendingPathComponent("vChewingFactoryData/vChewingFactoryDatabase.sqlite")
|
||||
return FileManager.default.fileExists(atPath: containerResultURL.path, isDirectory: nil) ? containerResultURL.path : factoryResultURL.path
|
||||
}
|
||||
|
||||
// MARK: - 使用者語彙檔案的具體檔案名稱路徑定義
|
||||
|
@ -105,14 +75,6 @@ public extension LMMgr {
|
|||
return URL(fileURLWithPath: dataFolderPath(isDefaultFolder: false)).appendingPathComponent(fileName)
|
||||
}
|
||||
|
||||
/// 逐字選字模式候選字詞順序資料路徑。該資料出自倚天中文 DOS 系統。
|
||||
/// - Parameter mode: 簡繁體輸入模式。
|
||||
/// - Returns: 資料路徑(URL)。
|
||||
static func etenSCPCSequencesURL(_ mode: Shared.InputMode) -> URL {
|
||||
let fileName = (mode == .imeModeCHT) ? "sequenceDataFromEtenDOS-cht" : "sequenceDataFromEtenDOS-chs"
|
||||
return URL(fileURLWithPath: getBundleDataPath(fileName, factory: true, ext: "json"))
|
||||
}
|
||||
|
||||
/// 使用者波浪符號選單資料路徑。
|
||||
/// - Returns: 資料路徑(URL)。
|
||||
static func userSymbolMenuDataURL() -> URL {
|
||||
|
|
|
@ -168,13 +168,6 @@ import SwiftExtension
|
|||
@AppProperty(key: UserDef.kClientsIMKTextInputIncapable.rawValue, defaultValue: kDefaultClientsIMKTextInputIncapable)
|
||||
public dynamic var clientsIMKTextInputIncapable: [String: Bool]
|
||||
|
||||
@AppProperty(key: UserDef.kOnlyLoadFactoryLangModelsIfNeeded.rawValue, defaultValue: true)
|
||||
public dynamic var onlyLoadFactoryLangModelsIfNeeded: Bool {
|
||||
didSet {
|
||||
if !onlyLoadFactoryLangModelsIfNeeded { LMMgr.loadDataModelsOnAppDelegate() }
|
||||
}
|
||||
}
|
||||
|
||||
@AppProperty(key: UserDef.kShowTranslatedStrokesInCompositionBuffer.rawValue, defaultValue: true)
|
||||
public dynamic var showTranslatedStrokesInCompositionBuffer: Bool
|
||||
|
||||
|
|
Binary file not shown.
|
@ -179,7 +179,7 @@ public class SessionCtl: IMKInputController {
|
|||
PrefMgr.shared.mostRecentInputMode = newValue.rawValue
|
||||
}
|
||||
didSet {
|
||||
if PrefMgr.shared.onlyLoadFactoryLangModelsIfNeeded { LMMgr.loadDataModel(inputMode) }
|
||||
/// SQLite 資料庫是在 AppDelegate 階段就載入的,所以這裡不需要再 Lazy-Load。
|
||||
if oldValue != inputMode, inputMode != .imeModeNULL {
|
||||
/// 先重置輸入調度模組,不然會因為之後的命令而導致該命令無法正常執行。
|
||||
resetInputHandler()
|
||||
|
@ -406,11 +406,9 @@ public extension SessionCtl {
|
|||
}
|
||||
let status = "NotificationSwitchRevolver".localized
|
||||
DispatchQueue.main.async {
|
||||
if LMMgr.lmCHT.isCoreLMLoaded, LMMgr.lmCHS.isCoreLMLoaded {
|
||||
Notifier.notify(
|
||||
message: nowMode.reversed.localizedDescription + "\n" + status
|
||||
)
|
||||
}
|
||||
Notifier.notify(
|
||||
message: nowMode.reversed.localizedDescription + "\n" + status
|
||||
)
|
||||
}
|
||||
client.selectMode(nowMode.reversed.rawValue)
|
||||
}
|
||||
|
|
|
@ -115,7 +115,7 @@ public extension SessionCtl {
|
|||
guard let inputHandler = inputHandler, client() != nil else { return false }
|
||||
|
||||
/// 除非核心辭典有載入,否則一律蜂鳴。
|
||||
if !inputHandler.currentLM.isCoreLMLoaded {
|
||||
if !LMMgr.isCoreDBConnected {
|
||||
if (event as InputSignalProtocol).isReservedKey { return false }
|
||||
var newState: IMEStateProtocol = IMEState.ofEmpty()
|
||||
newState.tooltip = NSLocalizedString("Factory dictionary not loaded yet.", comment: "")
|
||||
|
|
|
@ -26,9 +26,6 @@ public struct VwrSettingsPaneDictionary: View {
|
|||
@AppStorage(wrappedValue: false, UserDef.kUseExternalFactoryDict.rawValue)
|
||||
private var useExternalFactoryDict: Bool
|
||||
|
||||
@AppStorage(wrappedValue: true, UserDef.kOnlyLoadFactoryLangModelsIfNeeded.rawValue)
|
||||
private var onlyLoadFactoryLangModelsIfNeeded: Bool
|
||||
|
||||
@AppStorage(wrappedValue: false, UserDef.kCNS11643Enabled.rawValue)
|
||||
private var cns11643Enabled: Bool
|
||||
|
||||
|
@ -167,7 +164,7 @@ public struct VwrSettingsPaneDictionary: View {
|
|||
Toggle(
|
||||
LocalizedStringKey("Read external factory dictionary files if possible"),
|
||||
isOn: $useExternalFactoryDict.onChange {
|
||||
LMMgr.reloadFactoryDictionaryFiles()
|
||||
LMMgr.connectCoreDB()
|
||||
}
|
||||
)
|
||||
Text(
|
||||
|
@ -177,12 +174,6 @@ public struct VwrSettingsPaneDictionary: View {
|
|||
)
|
||||
.settingsDescription()
|
||||
}
|
||||
Toggle(
|
||||
LocalizedStringKey("Only load factory language models if needed"),
|
||||
isOn: $onlyLoadFactoryLangModelsIfNeeded.onChange {
|
||||
if !onlyLoadFactoryLangModelsIfNeeded { LMMgr.loadDataModelsOnAppDelegate() }
|
||||
}
|
||||
)
|
||||
Toggle(
|
||||
LocalizedStringKey("Enable CNS11643 Support (2023-05-19)"),
|
||||
isOn: $cns11643Enabled.onChange {
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
// (c) 2021 and onwards The vChewing Project (MIT-NTL License).
|
||||
// ====================
|
||||
// This code is released under the MIT license (SPDX-License-Identifier: MIT)
|
||||
// ... with NTL restriction stating that:
|
||||
// No trademark license is granted to use the trade names, trademarks, service
|
||||
// marks, or product names of Contributor, except as required to fulfill notice
|
||||
// requirements defined in MIT License.
|
||||
|
||||
import LangModelAssembly
|
||||
import MainAssembly
|
||||
|
||||
// MARK: - Converting Sample String Data to LMCoreJSON Instance.
|
||||
|
||||
extension String {
|
||||
func toDictMap(swapKeyValue: Bool = false, encrypt: Bool = false) -> [String: [String]] {
|
||||
var theDict = [String: [String]]()
|
||||
enumerateLines { currentLine, _ in
|
||||
if currentLine.isEmpty || currentLine.hasPrefix("#") {
|
||||
return
|
||||
}
|
||||
let linestream = currentLine.split(separator: " ")
|
||||
let col0 = String(linestream[0])
|
||||
let col1 = String(linestream[1])
|
||||
let col2: Double? = Double(linestream[2])
|
||||
var key = swapKeyValue ? col1 : col0
|
||||
if encrypt {
|
||||
key = vChewingLM.LMCoreJSON.cnvPhonabetToASCII(key)
|
||||
}
|
||||
var storedValue = swapKeyValue ? col0 : col1
|
||||
if let col2 = col2 {
|
||||
storedValue.insert(contentsOf: "\(col2.description) ", at: storedValue.startIndex)
|
||||
}
|
||||
theDict[key, default: []].append(storedValue)
|
||||
}
|
||||
return theDict
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Allow LMInstantiator to Load Test Data.
|
||||
|
||||
extension vChewingLM.LMInstantiator {
|
||||
static func construct(
|
||||
isCHS: Bool = false, completionHandler: @escaping (_ this: vChewingLM.LMInstantiator) -> Void
|
||||
) -> vChewingLM.LMInstantiator {
|
||||
let this = vChewingLM.LMInstantiator(isCHS: isCHS)
|
||||
completionHandler(this)
|
||||
return this
|
||||
}
|
||||
|
||||
func loadTestData() {
|
||||
resetFactoryJSONModels()
|
||||
loadLanguageModel(
|
||||
json: (
|
||||
dict: strSampleDataFactoryCore.toDictMap(swapKeyValue: false, encrypt: true),
|
||||
path: "/dev/null"
|
||||
)
|
||||
)
|
||||
loadSymbolData(
|
||||
json: (
|
||||
dict: strSampleDataFactorySymbol.toDictMap(swapKeyValue: true, encrypt: true),
|
||||
path: "/dev/null"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -34,8 +34,11 @@ func vCTestLog(_ str: String) {
|
|||
/// 該單元測試使用獨立的語彙資料,因此會在選字時的候選字
|
||||
/// 順序等方面與威注音輸入法實際使用時的體驗有差異。
|
||||
class MainAssemblyTests: XCTestCase {
|
||||
let testUOM = vChewingLM.LMUserOverride(dataURL: .init(fileURLWithPath: "/dev/null"))
|
||||
var testLM = vChewingLM.LMInstantiator.construct { $0.loadTestData() }
|
||||
let testUOM = LangModelAssembly.vChewingLM.LMUserOverride(dataURL: .init(fileURLWithPath: "/dev/null"))
|
||||
var testLM = LangModelAssembly.vChewingLM.LMInstantiator.construct { _ in
|
||||
vChewingLM.LMInstantiator.connectToTestSQLDB()
|
||||
}
|
||||
|
||||
static let testServer = IMKServer(name: "org.atelierInmu.vChewing.MainAssembly.UnitTests_Connection", bundleIdentifier: "org.atelierInmu.vChewing.MainAssembly.UnitTests")
|
||||
|
||||
static var _testHandler: InputHandler?
|
||||
|
@ -102,3 +105,13 @@ class MainAssemblyTests: XCTestCase {
|
|||
testSession.deactivateServer(testClient)
|
||||
}
|
||||
}
|
||||
|
||||
extension vChewingLM.LMInstantiator {
|
||||
static func construct(
|
||||
isCHS: Bool = false, completionHandler: @escaping (_ this: vChewingLM.LMInstantiator) -> Void
|
||||
) -> vChewingLM.LMInstantiator {
|
||||
let this = vChewingLM.LMInstantiator(isCHS: isCHS)
|
||||
completionHandler(this)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,10 +316,6 @@ struct ContentView_Previews: PreviewProvider {
|
|||
}
|
||||
}
|
||||
|
||||
public extension vChewingLM.ReplacableUserDataType {
|
||||
var localizedDescription: String { NSLocalizedString(rawValue, comment: "") }
|
||||
}
|
||||
|
||||
public enum PETerms {
|
||||
public enum AddPhrases: String {
|
||||
case locPhrase = "Phrase"
|
||||
|
|
|
@ -13,5 +13,4 @@ import AppKit
|
|||
@objcMembers public class Broadcaster: NSObject {
|
||||
public static var shared = Broadcaster()
|
||||
public dynamic var eventForReloadingPhraseEditor = UUID()
|
||||
public dynamic var eventForReloadingRevLookupData = UUID()
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@ public protocol PrefMgrProtocol {
|
|||
var trimUnfinishedReadingsOnCommit: Bool { get set }
|
||||
var alwaysShowTooltipTextsHorizontally: Bool { get set }
|
||||
var clientsIMKTextInputIncapable: [String: Bool] { get set }
|
||||
var onlyLoadFactoryLangModelsIfNeeded: Bool { get set }
|
||||
var useSpaceToCommitHighlightedSCPCCandidate: Bool { get set }
|
||||
var enableMouseScrollingForTDKCandidatesCocoa: Bool { get set }
|
||||
var disableSegmentedThickUnderlineInMarkingModeForManagedClients: Bool { get set }
|
||||
|
|
|
@ -87,7 +87,6 @@ public enum UserDef: String, CaseIterable {
|
|||
case kTrimUnfinishedReadingsOnCommit = "TrimUnfinishedReadingsOnCommit"
|
||||
case kAlwaysShowTooltipTextsHorizontally = "AlwaysShowTooltipTextsHorizontally"
|
||||
case kClientsIMKTextInputIncapable = "ClientsIMKTextInputIncapable"
|
||||
case kOnlyLoadFactoryLangModelsIfNeeded = "OnlyLoadFactoryLangModelsIfNeeded"
|
||||
case kShowTranslatedStrokesInCompositionBuffer = "ShowTranslatedStrokesInCompositionBuffer"
|
||||
case kForceCassetteChineseConversion = "ForceCassetteChineseConversion"
|
||||
case kShowReverseLookupInCandidateUI = "ShowReverseLookupInCandidateUI"
|
||||
|
@ -205,7 +204,6 @@ public extension UserDef {
|
|||
case .kTrimUnfinishedReadingsOnCommit: return .bool
|
||||
case .kAlwaysShowTooltipTextsHorizontally: return .bool
|
||||
case .kClientsIMKTextInputIncapable: return .dictionary
|
||||
case .kOnlyLoadFactoryLangModelsIfNeeded: return .bool
|
||||
case .kShowTranslatedStrokesInCompositionBuffer: return .bool
|
||||
case .kForceCassetteChineseConversion: return .integer
|
||||
case .kShowReverseLookupInCandidateUI: return .bool
|
||||
|
@ -441,7 +439,6 @@ public extension UserDef {
|
|||
case .kTrimUnfinishedReadingsOnCommit: return .init(userDef: self, shortTitle: "Trim unfinished readings / strokes on commit")
|
||||
case .kAlwaysShowTooltipTextsHorizontally: return .init(userDef: self, shortTitle: "Always show tooltip texts horizontally")
|
||||
case .kClientsIMKTextInputIncapable: return nil
|
||||
case .kOnlyLoadFactoryLangModelsIfNeeded: return .init(userDef: self, shortTitle: "Only load factory language models if needed")
|
||||
case .kShowTranslatedStrokesInCompositionBuffer: return .init(userDef: self, shortTitle: "Show translated strokes in composition buffer")
|
||||
case .kForceCassetteChineseConversion: return .init(
|
||||
userDef: self,
|
||||
|
|
|
@ -334,7 +334,7 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate {
|
|||
}
|
||||
|
||||
@IBAction func toggledExternalFactoryPlistDataOnOff(_: NSButton) {
|
||||
LMMgr.reloadFactoryDictionaryFiles()
|
||||
LMMgr.connectCoreDB()
|
||||
}
|
||||
|
||||
@IBAction func resetSpecifiedUserDataFolder(_: Any) {
|
||||
|
|
|
@ -26,28 +26,9 @@ class CtlRevLookupWindow: NSWindowController, NSWindowDelegate {
|
|||
shared.showWindow(shared)
|
||||
NSApp.popup()
|
||||
}
|
||||
|
||||
override func windowDidLoad() {
|
||||
super.windowDidLoad()
|
||||
observation = Broadcaster.shared.observe(\.eventForReloadingRevLookupData, options: [.new]) { _, _ in
|
||||
FrmRevLookupWindow.reloadData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FrmRevLookupWindow: NSWindow {
|
||||
typealias LMRevLookup = vChewingLM.LMRevLookup
|
||||
|
||||
static var lmRevLookupCore = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup"))
|
||||
|
||||
// 全字庫資料接近十萬筆索引,只放到單個 Dictionary 內的話、每次查詢時都會把輸入法搞崩潰。只能分卷處理。
|
||||
static var lmRevLookupCNS1 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS1"))
|
||||
static var lmRevLookupCNS2 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS2"))
|
||||
static var lmRevLookupCNS3 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS3"))
|
||||
static var lmRevLookupCNS4 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS4"))
|
||||
static var lmRevLookupCNS5 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS5"))
|
||||
static var lmRevLookupCNS6 = LMRevLookup(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS6"))
|
||||
|
||||
public lazy var inputField = NSTextField()
|
||||
public lazy var resultView = NSTextView()
|
||||
private lazy var clipView = NSClipView()
|
||||
|
@ -56,13 +37,7 @@ class FrmRevLookupWindow: NSWindow {
|
|||
private lazy var view = NSView()
|
||||
|
||||
static func reloadData() {
|
||||
DispatchQueue.main.async { lmRevLookupCore = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS1 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS1")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS2 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS2")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS3 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS3")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS4 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS4")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS5 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS5")) }
|
||||
DispatchQueue.main.async { lmRevLookupCNS6 = .init(data: LMMgr.getDictionaryData("data-bpmf-reverse-lookup-CNS6")) }
|
||||
LMMgr.connectCoreDB()
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -176,17 +151,7 @@ class FrmRevLookupWindow: NSWindow {
|
|||
strBuilder.append("Maximum 15 results returnable.".localized + "\n")
|
||||
break theLoop
|
||||
}
|
||||
var arrResult = Self.lmRevLookupCore.query(with: char) ?? []
|
||||
// 一般情況下,威注音語彙庫的倉庫內的全字庫資料檔案有做過排序,所以每個分卷的索引都是不重複的。
|
||||
arrResult +=
|
||||
Self.lmRevLookupCNS1.query(with: char)
|
||||
?? Self.lmRevLookupCNS2.query(with: char)
|
||||
?? Self.lmRevLookupCNS3.query(with: char)
|
||||
?? Self.lmRevLookupCNS4.query(with: char)
|
||||
?? Self.lmRevLookupCNS5.query(with: char)
|
||||
?? Self.lmRevLookupCNS6.query(with: char)
|
||||
?? []
|
||||
arrResult = arrResult.deduplicated
|
||||
let arrResult = vChewingLM.LMInstantiator.getFactoryReverseLookupData(with: char)?.deduplicated ?? []
|
||||
if !arrResult.isEmpty {
|
||||
strBuilder.append(char + "\t")
|
||||
strBuilder.append(arrResult.joined(separator: ", "))
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22503" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="22155" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22503"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22155"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
|
@ -1492,14 +1492,14 @@
|
|||
</view>
|
||||
<userDefaultsController representsSharedInstance="YES" id="32"/>
|
||||
<view id="Rnp-LM-RIF" userLabel="vwrDictionary">
|
||||
<rect key="frame" x="0.0" y="0.0" width="577" height="288"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="577" height="278"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" translatesAutoresizingMaskIntoConstraints="NO" id="6xK-1z-A14">
|
||||
<rect key="frame" x="20" y="28" width="530" height="240"/>
|
||||
<rect key="frame" x="20" y="39" width="530" height="219"/>
|
||||
<subviews>
|
||||
<textField autoresizesSubviews="NO" focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FUV-qx-xkC">
|
||||
<rect key="frame" x="-2" y="225" width="388" height="15"/>
|
||||
<rect key="frame" x="-2" y="204" width="388" height="15"/>
|
||||
<textFieldCell key="cell" controlSize="small" lineBreakMode="clipping" title="Choose your desired user data folder path. Will be omitted if invalid." id="wN3-k3-b2a">
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
|
@ -1507,7 +1507,7 @@
|
|||
</textFieldCell>
|
||||
</textField>
|
||||
<stackView distribution="fill" orientation="horizontal" alignment="bottom" spacing="0.0" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" translatesAutoresizingMaskIntoConstraints="NO" id="uUS-Ni-jFP">
|
||||
<rect key="frame" x="0.0" y="195" width="530" height="22"/>
|
||||
<rect key="frame" x="0.0" y="174" width="530" height="22"/>
|
||||
<subviews>
|
||||
<pathControl verticalHuggingPriority="750" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="p4U-xB-kEs">
|
||||
<rect key="frame" x="0.0" y="0.0" width="470" height="20"/>
|
||||
|
@ -1570,7 +1570,7 @@
|
|||
</customSpacing>
|
||||
</stackView>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qFh-Qn-v9j">
|
||||
<rect key="frame" x="-6" y="164" width="249" height="27"/>
|
||||
<rect key="frame" x="-6" y="143" width="249" height="27"/>
|
||||
<buttonCell key="cell" type="push" title="Import Yahoo! KeyKey User Dictionary File" bezelStyle="rounded" alignment="center" controlSize="small" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="btnImportFromKimoTxt">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
@ -1580,10 +1580,10 @@
|
|||
</connections>
|
||||
</button>
|
||||
<stackView distribution="fill" orientation="vertical" alignment="leading" spacing="5" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" translatesAutoresizingMaskIntoConstraints="NO" id="i2J-0A-opq">
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="163"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="450" height="142"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="p7V-IN-OTr">
|
||||
<rect key="frame" x="-1" y="146.5" width="336" height="17"/>
|
||||
<rect key="frame" x="-1" y="125.5" width="336" height="17"/>
|
||||
<buttonCell key="cell" type="check" title="Automatically reload user data files if changes detected" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="f8i-69-zxm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
|
@ -1596,7 +1596,7 @@
|
|||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="MJ6-U4-qyl">
|
||||
<rect key="frame" x="-1" y="125.5" width="290" height="17"/>
|
||||
<rect key="frame" x="-1" y="104.5" width="290" height="17"/>
|
||||
<buttonCell key="cell" type="check" title="Read external factory dictionary files if possible" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="fEA-Bp-Ayn" userLabel="chkUseExternalFactoryDict">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
|
@ -1609,20 +1609,6 @@
|
|||
<binding destination="32" name="value" keyPath="values.UseExternalFactoryDict" id="1Gp-Ax-mRK"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="ZQK-w3-FRb">
|
||||
<rect key="frame" x="-1" y="104.5" width="274" height="17"/>
|
||||
<buttonCell key="cell" type="check" title="Only load factory language models if needed" bezelStyle="regularSquare" imagePosition="left" controlSize="small" state="on" inset="2" id="chkOnlyLoadFactoryLangModelsIfNeeded" userLabel="chkOnlyLoadFactoryLangModelsIfNeeded">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="cellTitle"/>
|
||||
</buttonCell>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="16" id="anX-Rc-sxQ"/>
|
||||
</constraints>
|
||||
<connections>
|
||||
<action selector="toggleSymbolInputEnabled:" target="-2" id="AeU-Ii-tOF"/>
|
||||
<binding destination="32" name="value" keyPath="values.OnlyLoadFactoryLangModelsIfNeeded" id="TJ2-Fj-pca"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="Yaj-QY-7xV" userLabel="chkCNSSupport">
|
||||
<rect key="frame" x="-1" y="83.5" width="252" height="17"/>
|
||||
<buttonCell key="cell" type="check" title="Enable CNS11643 Support (2023-05-19)" bezelStyle="regularSquare" imagePosition="left" controlSize="small" inset="2" id="W24-T4-cg0">
|
||||
|
@ -1700,7 +1686,6 @@
|
|||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
<integer value="1000"/>
|
||||
</visibilityPriorities>
|
||||
<customSpacing>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
|
@ -1710,7 +1695,6 @@
|
|||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
<real value="3.4028234663852886e+38"/>
|
||||
</customSpacing>
|
||||
</stackView>
|
||||
</subviews>
|
||||
|
@ -1733,7 +1717,7 @@
|
|||
<constraint firstItem="6xK-1z-A14" firstAttribute="leading" secondItem="Rnp-LM-RIF" secondAttribute="leading" constant="20" symbolic="YES" id="zFW-2a-uC3"/>
|
||||
</constraints>
|
||||
<accessibility identifier="vwrDictionary"/>
|
||||
<point key="canvasLocation" x="-717.5" y="811"/>
|
||||
<point key="canvasLocation" x="-717.5" y="806"/>
|
||||
</view>
|
||||
<view wantsLayer="YES" id="nMV-YT-ex7" userLabel="vwrPhrases">
|
||||
<rect key="frame" x="0.0" y="0.0" width="577" height="508"/>
|
||||
|
@ -1853,7 +1837,7 @@
|
|||
<rect key="frame" x="0.0" y="71" width="538" height="370"/>
|
||||
<clipView key="contentView" drawsBackground="NO" id="x8s-wo-bxi">
|
||||
<rect key="frame" x="0.0" y="0.0" width="538" height="370"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textView wantsLayer="YES" importsGraphics="NO" richText="NO" verticallyResizable="YES" allowsUndo="YES" smartInsertDelete="YES" id="kSG-dz-P2N">
|
||||
<rect key="frame" x="0.0" y="0.0" width="538" height="370"/>
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
"chkFetchSuggestionsFromUserOverrideModel.title" = "Applying typing suggestions from half-life user override model";
|
||||
"chkHardenVerticalPunctuations.title" = "Harden vertical punctuations during vertical typing";
|
||||
"chkKeepReadingUponCompositionError.title" = "Allow backspace-editing miscomposed readings";
|
||||
"chkOnlyLoadFactoryLangModelsIfNeeded.title" = "Only load factory language models if needed";
|
||||
"chkShiftEisuToggleOffTogetherWithCapsLock.title" = "Sync the off state between Caps Lock and Shift / Eisu Alphanumerical Toggle";
|
||||
"chkUseFixedCandidateOrderOnSelection.title" = "Always use fixed listing order in candidate window";
|
||||
"clN-4A-iec.title" = "Use Space to confirm highlighted candidate in Per-Char Select Mode";
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
"chkFetchSuggestionsFromUserOverrideModel.title" = "入力中で臨時記憶モジュールからお薦めの候補を自動的に選ぶ";
|
||||
"chkHardenVerticalPunctuations.title" = "縦書きの時に、引用符・括弧などを強制的に縦書き文字と変換する(不推奨)";
|
||||
"chkKeepReadingUponCompositionError.title" = "効かぬ音読みを BackSpace で再編集";
|
||||
"chkOnlyLoadFactoryLangModelsIfNeeded.title" = "必要性を判断してから内蔵辞書を読み込む";
|
||||
"chkShiftEisuToggleOffTogetherWithCapsLock.title" = "「Shiftキー・JIS英数キー」による英数モードのオフ状態をCapsLockのオフ状態と同期する";
|
||||
"chkUseFixedCandidateOrderOnSelection.title" = "候補文字を固定順番で陳列する";
|
||||
"clN-4A-iec.title" = "全候補入力の場合、ハイライト候補を Space キーで確認";
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
"chkFetchSuggestionsFromUserOverrideModel.title" = "在敲字时自动套用来自半衰记忆模组的建议";
|
||||
"chkHardenVerticalPunctuations.title" = "在纵排书写时,强制转换标点为纵排形式(不推荐)";
|
||||
"chkKeepReadingUponCompositionError.title" = "允许对无效的读音使用 BackSpace 编辑";
|
||||
"chkOnlyLoadFactoryLangModelsIfNeeded.title" = "按需载入简繁体模式的原厂辞典资料";
|
||||
"chkShiftEisuToggleOffTogetherWithCapsLock.title" = "使「Shift 键 / JIS 英数键」英数模式的关闭状态与 Caps Lock 的关闭状态保持彼此同步";
|
||||
"chkUseFixedCandidateOrderOnSelection.title" = "以固定顺序来陈列选字窗内的候选字";
|
||||
"clN-4A-iec.title" = "在逐字选字模式当中使用空格键确认当前高亮候选字";
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
"chkFetchSuggestionsFromUserOverrideModel.title" = "在敲字時自動套用來自半衰記憶模組的建議";
|
||||
"chkHardenVerticalPunctuations.title" = "在縱排書寫時,強制轉換標點為縱排形式(不推薦)";
|
||||
"chkKeepReadingUponCompositionError.title" = "允許對無效的讀音使用 BackSpace 編輯";
|
||||
"chkOnlyLoadFactoryLangModelsIfNeeded.title" = "按需載入簡繁體模式的原廠辭典資料";
|
||||
"chkShiftEisuToggleOffTogetherWithCapsLock.title" = "使「Shift 鍵 / JIS 英数鍵」英數模式的關閉狀態與 Caps Lock 的關閉狀態保持彼此同步";
|
||||
"chkUseFixedCandidateOrderOnSelection.title" = "以固定順序來陳列選字窗內的候選字";
|
||||
"clN-4A-iec.title" = "在逐字選字模式當中使用空格鍵確認當前高亮候選字";
|
||||
|
|
|
@ -10,13 +10,7 @@
|
|||
5B09307628B6FC3B0021F8C5 /* shortcuts.html in Resources */ = {isa = PBXBuildFile; fileRef = 5B09307828B6FC3B0021F8C5 /* shortcuts.html */; };
|
||||
5B0EF55D28CDBF7100F8F7CE /* frmClientListMgr.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B0EF55C28CDBF7100F8F7CE /* frmClientListMgr.xib */; };
|
||||
5B0EF55F28CDBF8E00F8F7CE /* CtlClientListMgr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0EF55E28CDBF8E00F8F7CE /* CtlClientListMgr.swift */; };
|
||||
5B1C98B929436CEE0019B807 /* data-bpmf-reverse-lookup.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B1C98B729436CED0019B807 /* data-bpmf-reverse-lookup.json */; };
|
||||
5B253E7E2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS1.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E782945AF6700680C67 /* data-bpmf-reverse-lookup-CNS1.json */; };
|
||||
5B253E7F2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS3.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E792945AF6700680C67 /* data-bpmf-reverse-lookup-CNS3.json */; };
|
||||
5B253E802945AF6700680C67 /* data-bpmf-reverse-lookup-CNS2.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7A2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS2.json */; };
|
||||
5B253E812945AF6700680C67 /* data-bpmf-reverse-lookup-CNS6.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7B2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS6.json */; };
|
||||
5B253E822945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7C2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.json */; };
|
||||
5B253E832945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B253E7D2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.json */; };
|
||||
5B2CA1E62B130869002634EE /* vChewingFactoryDatabase.sqlite in Resources */ = {isa = PBXBuildFile; fileRef = 5B2CA1E52B130869002634EE /* vChewingFactoryDatabase.sqlite */; };
|
||||
5B30BF282944867800BD87A9 /* CtlRevLookupWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */; };
|
||||
5B40113928D7050D00A9D4CB /* Shared in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113828D7050D00A9D4CB /* Shared */; };
|
||||
5B40113C28D71C0100A9D4CB /* Uninstaller in Frameworks */ = {isa = PBXBuildFile; productRef = 5B40113B28D71C0100A9D4CB /* Uninstaller */; };
|
||||
|
@ -28,7 +22,6 @@
|
|||
5B765F09293A253C00122315 /* PhraseEditorUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5B765F08293A253C00122315 /* PhraseEditorUI */; };
|
||||
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; };
|
||||
5B7DA80328BF6BC600D7B2AD /* fixinstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5B7DA80228BF6BBA00D7B2AD /* fixinstall.sh */; };
|
||||
5B84579E2871AD2200C93B01 /* convdict.json in Resources */ = {isa = PBXBuildFile; fileRef = 5B84579C2871AD2200C93B01 /* convdict.json */; };
|
||||
5B963C9D28D5BFB800DCEE88 /* CocoaExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 5B963C9C28D5BFB800DCEE88 /* CocoaExtension */; };
|
||||
5B963CA328D5C23600DCEE88 /* SwiftExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 5B963CA228D5C23600DCEE88 /* SwiftExtension */; };
|
||||
5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */ = {isa = PBXBuildFile; productRef = 5B98114728D6198700CBC605 /* PinyinPhonaConverter */; };
|
||||
|
@ -54,17 +47,10 @@
|
|||
5BDB7A4128D4824A001AC277 /* Megrez in Frameworks */ = {isa = PBXBuildFile; productRef = 5BDB7A4028D4824A001AC277 /* Megrez */; };
|
||||
5BDB7A4528D4824A001AC277 /* ShiftKeyUpChecker in Frameworks */ = {isa = PBXBuildFile; productRef = 5BDB7A4428D4824A001AC277 /* ShiftKeyUpChecker */; };
|
||||
5BDB7A4728D4824A001AC277 /* Tekkon in Frameworks */ = {isa = PBXBuildFile; productRef = 5BDB7A4628D4824A001AC277 /* Tekkon */; };
|
||||
5BE5D9952ACAEFDE009A732C /* sequenceDataFromEtenDOS-cht.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BE5D9932ACAEFDE009A732C /* sequenceDataFromEtenDOS-cht.json */; };
|
||||
5BE5D9962ACAEFDE009A732C /* sequenceDataFromEtenDOS-chs.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BE5D9942ACAEFDE009A732C /* sequenceDataFromEtenDOS-chs.json */; };
|
||||
5BE63A2D2AC5B8A6009AFC0C /* InstallerShared.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE63A2C2AC5B882009AFC0C /* InstallerShared.swift */; };
|
||||
5BE63A2E2AC5B8A8009AFC0C /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE63A2B2AC5B7D3009AFC0C /* MainView.swift */; };
|
||||
5BE63A2F2AC5B8A9009AFC0C /* vChewingInstallerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE63A2A2AC5B7C3009AFC0C /* vChewingInstallerApp.swift */; };
|
||||
5BE63A332AC5F4F8009AFC0C /* MainViewImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE63A322AC5F4F8009AFC0C /* MainViewImpl.swift */; };
|
||||
5BEDB721283B4C250078EB25 /* data-cns.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71D283B4AEA0078EB25 /* data-cns.json */; };
|
||||
5BEDB722283B4C250078EB25 /* data-zhuyinwen.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.json */; };
|
||||
5BEDB723283B4C250078EB25 /* data-cht.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB720283B4AEA0078EB25 /* data-cht.json */; };
|
||||
5BEDB724283B4C250078EB25 /* data-symbols.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71E283B4AEA0078EB25 /* data-symbols.json */; };
|
||||
5BEDB725283B4C250078EB25 /* data-chs.json in Resources */ = {isa = PBXBuildFile; fileRef = 5BEDB71C283B4AEA0078EB25 /* data-chs.json */; };
|
||||
5BF9DA2728840E6200DBD48E /* template-usersymbolphrases.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */; };
|
||||
5BF9DA2828840E6200DBD48E /* template-exclusions.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2328840E6200DBD48E /* template-exclusions.txt */; };
|
||||
5BF9DA2928840E6200DBD48E /* template-associatedPhrases-chs.txt in Resources */ = {isa = PBXBuildFile; fileRef = 5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */; };
|
||||
|
@ -138,14 +124,8 @@
|
|||
5B18BA7227C7BD8B0056EB19 /* LICENSE.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
|
||||
5B18BA7327C7BD8C0056EB19 /* LICENSE-JPN.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-JPN.txt"; sourceTree = "<group>"; };
|
||||
5B18BA7427C7BD8C0056EB19 /* LICENSE-CHT.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "LICENSE-CHT.txt"; sourceTree = "<group>"; };
|
||||
5B1C98B729436CED0019B807 /* data-bpmf-reverse-lookup.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup.json"; path = "Data/data-bpmf-reverse-lookup.json"; sourceTree = "<group>"; };
|
||||
5B20430B28BEFC0C00BFC6FD /* vChewing.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewing.entitlements; sourceTree = "<group>"; };
|
||||
5B253E782945AF6700680C67 /* data-bpmf-reverse-lookup-CNS1.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS1.json"; path = "Data/data-bpmf-reverse-lookup-CNS1.json"; sourceTree = "<group>"; };
|
||||
5B253E792945AF6700680C67 /* data-bpmf-reverse-lookup-CNS3.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS3.json"; path = "Data/data-bpmf-reverse-lookup-CNS3.json"; sourceTree = "<group>"; };
|
||||
5B253E7A2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS2.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS2.json"; path = "Data/data-bpmf-reverse-lookup-CNS2.json"; sourceTree = "<group>"; };
|
||||
5B253E7B2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS6.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS6.json"; path = "Data/data-bpmf-reverse-lookup-CNS6.json"; sourceTree = "<group>"; };
|
||||
5B253E7C2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS4.json"; path = "Data/data-bpmf-reverse-lookup-CNS4.json"; sourceTree = "<group>"; };
|
||||
5B253E7D2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-bpmf-reverse-lookup-CNS5.json"; path = "Data/data-bpmf-reverse-lookup-CNS5.json"; sourceTree = "<group>"; };
|
||||
5B2CA1E52B130869002634EE /* vChewingFactoryDatabase.sqlite */ = {isa = PBXFileReference; lastKnownFileType = file; name = vChewingFactoryDatabase.sqlite; path = Data/Build/Release/vChewingFactoryDatabase.sqlite; sourceTree = "<group>"; };
|
||||
5B2DB17127AF8771006D874E /* Makefile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.make; name = Makefile; path = Data/Makefile; sourceTree = "<group>"; };
|
||||
5B30BF272944867800BD87A9 /* CtlRevLookupWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CtlRevLookupWindow.swift; sourceTree = "<group>"; };
|
||||
5B30F11227BA568800484E24 /* vChewingKeyLayout.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = vChewingKeyLayout.bundle; sourceTree = "<group>"; };
|
||||
|
@ -161,7 +141,6 @@
|
|||
5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = "<group>"; };
|
||||
5B7BC4B227AFFC0B00F66C24 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/frmPrefWindow.strings; sourceTree = "<group>"; };
|
||||
5B7DA80228BF6BBA00D7B2AD /* fixinstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; lineEnding = 0; path = fixinstall.sh; sourceTree = "<group>"; };
|
||||
5B84579C2871AD2200C93B01 /* convdict.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = convdict.json; sourceTree = "<group>"; };
|
||||
5B963C9B28D5BE4100DCEE88 /* vChewing_CocoaExtension */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_CocoaExtension; path = Packages/vChewing_CocoaExtension; sourceTree = "<group>"; };
|
||||
5B963C9E28D5C14600DCEE88 /* vChewing_Shared */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_Shared; path = Packages/vChewing_Shared; sourceTree = "<group>"; };
|
||||
5B963CA128D5C22D00DCEE88 /* vChewing_SwiftExtension */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_SwiftExtension; path = Packages/vChewing_SwiftExtension; sourceTree = "<group>"; };
|
||||
|
@ -194,18 +173,11 @@
|
|||
5BDCBB4827B4F6C600D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4927B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5BDCBB4A27B4F6C700D0CC59 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
5BE5D9932ACAEFDE009A732C /* sequenceDataFromEtenDOS-cht.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "sequenceDataFromEtenDOS-cht.json"; sourceTree = "<group>"; };
|
||||
5BE5D9942ACAEFDE009A732C /* sequenceDataFromEtenDOS-chs.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "sequenceDataFromEtenDOS-chs.json"; sourceTree = "<group>"; };
|
||||
5BE63A2A2AC5B7C3009AFC0C /* vChewingInstallerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = vChewingInstallerApp.swift; sourceTree = "<group>"; };
|
||||
5BE63A2B2AC5B7D3009AFC0C /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||
5BE63A2C2AC5B882009AFC0C /* InstallerShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstallerShared.swift; sourceTree = "<group>"; };
|
||||
5BE63A322AC5F4F8009AFC0C /* MainViewImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewImpl.swift; sourceTree = "<group>"; };
|
||||
5BE8A8C4281EE65300197741 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = "<group>"; };
|
||||
5BEDB71C283B4AEA0078EB25 /* data-chs.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-chs.json"; path = "Data/data-chs.json"; sourceTree = "<group>"; };
|
||||
5BEDB71D283B4AEA0078EB25 /* data-cns.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-cns.json"; path = "Data/data-cns.json"; sourceTree = "<group>"; };
|
||||
5BEDB71E283B4AEA0078EB25 /* data-symbols.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-symbols.json"; path = "Data/data-symbols.json"; sourceTree = "<group>"; };
|
||||
5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-zhuyinwen.json"; path = "Data/data-zhuyinwen.json"; sourceTree = "<group>"; };
|
||||
5BEDB720283B4AEA0078EB25 /* data-cht.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; name = "data-cht.json"; path = "Data/data-cht.json"; sourceTree = "<group>"; };
|
||||
5BF255CD28B2694E003ECB60 /* vChewing-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "vChewing-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
5BF4A44628E5820C002AF9C5 /* vChewingInstaller.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = vChewingInstaller.entitlements; sourceTree = "<group>"; };
|
||||
5BF9DA2228840E6200DBD48E /* template-usersymbolphrases.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; lineEnding = 0; path = "template-usersymbolphrases.txt"; sourceTree = "<group>"; usesTabs = 0; };
|
||||
|
@ -311,10 +283,7 @@
|
|||
5B62A33027AE78E500A19448 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B84579C2871AD2200C93B01 /* convdict.json */,
|
||||
5B09307828B6FC3B0021F8C5 /* shortcuts.html */,
|
||||
5BE5D9942ACAEFDE009A732C /* sequenceDataFromEtenDOS-chs.json */,
|
||||
5BE5D9932ACAEFDE009A732C /* sequenceDataFromEtenDOS-cht.json */,
|
||||
5BF9DA2428840E6200DBD48E /* template-associatedPhrases-chs.txt */,
|
||||
5BF9DA2C2884247800DBD48E /* template-associatedPhrases-cht.txt */,
|
||||
5BF9DA2328840E6200DBD48E /* template-exclusions.txt */,
|
||||
|
@ -355,18 +324,7 @@
|
|||
5B62A35027AE7F6600A19448 /* Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5B253E782945AF6700680C67 /* data-bpmf-reverse-lookup-CNS1.json */,
|
||||
5B253E7A2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS2.json */,
|
||||
5B253E792945AF6700680C67 /* data-bpmf-reverse-lookup-CNS3.json */,
|
||||
5B253E7C2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.json */,
|
||||
5B253E7D2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.json */,
|
||||
5B253E7B2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS6.json */,
|
||||
5B1C98B729436CED0019B807 /* data-bpmf-reverse-lookup.json */,
|
||||
5BEDB71C283B4AEA0078EB25 /* data-chs.json */,
|
||||
5BEDB720283B4AEA0078EB25 /* data-cht.json */,
|
||||
5BEDB71D283B4AEA0078EB25 /* data-cns.json */,
|
||||
5BEDB71E283B4AEA0078EB25 /* data-symbols.json */,
|
||||
5BEDB71F283B4AEA0078EB25 /* data-zhuyinwen.json */,
|
||||
5B2CA1E52B130869002634EE /* vChewingFactoryDatabase.sqlite */,
|
||||
5B2DB17127AF8771006D874E /* Makefile */,
|
||||
);
|
||||
name = Data;
|
||||
|
@ -648,7 +606,6 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5B84579E2871AD2200C93B01 /* convdict.json in Resources */,
|
||||
D4E33D8A27A838CF006DB1CF /* Localizable.strings in Resources */,
|
||||
5B70F4EC2A0BE900005EA8C4 /* MenuIcon-TCVIM@2x.png in Resources */,
|
||||
5BF9DA2828840E6200DBD48E /* template-exclusions.txt in Resources */,
|
||||
|
@ -659,22 +616,15 @@
|
|||
6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */,
|
||||
5B70F4EA2A0BE900005EA8C4 /* MenuIcon-SCVIM.png in Resources */,
|
||||
D4E33D8F27A838F0006DB1CF /* InfoPlist.strings in Resources */,
|
||||
5BEDB723283B4C250078EB25 /* data-cht.json in Resources */,
|
||||
5BEDB721283B4C250078EB25 /* data-cns.json in Resources */,
|
||||
5BBC2D9F28F51C0400C986F6 /* LICENSE-CHT.txt in Resources */,
|
||||
5BE5D9952ACAEFDE009A732C /* sequenceDataFromEtenDOS-cht.json in Resources */,
|
||||
5BF9DA2D288427E000DBD48E /* template-associatedPhrases-cht.txt in Resources */,
|
||||
5BBC2D9E28F51C0400C986F6 /* LICENSE.txt in Resources */,
|
||||
5BEDB725283B4C250078EB25 /* data-chs.json in Resources */,
|
||||
5B2CA1E62B130869002634EE /* vChewingFactoryDatabase.sqlite in Resources */,
|
||||
5BF9DA2928840E6200DBD48E /* template-associatedPhrases-chs.txt in Resources */,
|
||||
5B09307628B6FC3B0021F8C5 /* shortcuts.html in Resources */,
|
||||
5BF9DA2B28840E6200DBD48E /* template-userphrases.txt in Resources */,
|
||||
5BEDB722283B4C250078EB25 /* data-zhuyinwen.json in Resources */,
|
||||
5BF9DA2728840E6200DBD48E /* template-usersymbolphrases.txt in Resources */,
|
||||
5B1C98B929436CEE0019B807 /* data-bpmf-reverse-lookup.json in Resources */,
|
||||
5BBC2DA128F51C0400C986F6 /* LICENSE-CHS.txt in Resources */,
|
||||
5BE5D9962ACAEFDE009A732C /* sequenceDataFromEtenDOS-chs.json in Resources */,
|
||||
5BEDB724283B4C250078EB25 /* data-symbols.json in Resources */,
|
||||
5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */,
|
||||
5BF9DA2A28840E6200DBD48E /* template-replacements.txt in Resources */,
|
||||
6A187E2616004C5900466B2E /* MainMenu.xib in Resources */,
|
||||
|
@ -682,12 +632,6 @@
|
|||
5BC2652227E04B7E00700291 /* uninstall.sh in Resources */,
|
||||
5BAD0CD527D701F6003D127F /* vChewingKeyLayout.bundle in Resources */,
|
||||
5BBC2DA028F51C0400C986F6 /* LICENSE-JPN.txt in Resources */,
|
||||
5B253E7E2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS1.json in Resources */,
|
||||
5B253E802945AF6700680C67 /* data-bpmf-reverse-lookup-CNS2.json in Resources */,
|
||||
5B253E7F2945AF6700680C67 /* data-bpmf-reverse-lookup-CNS3.json in Resources */,
|
||||
5B253E822945AF6700680C67 /* data-bpmf-reverse-lookup-CNS4.json in Resources */,
|
||||
5B253E832945AF6700680C67 /* data-bpmf-reverse-lookup-CNS5.json in Resources */,
|
||||
5B253E812945AF6700680C67 /* data-bpmf-reverse-lookup-CNS6.json in Resources */,
|
||||
5B70F4EB2A0BE900005EA8C4 /* MenuIcon-SCVIM@2x.png in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -772,6 +716,11 @@
|
|||
"$(SRCROOT)/Source/Data/data-bpmf-reverse-lookup-CNS4.json",
|
||||
"$(SRCROOT)/Source/Data/data-bpmf-reverse-lookup-CNS5.json",
|
||||
"$(SRCROOT)/Source/Data/data-bpmf-reverse-lookup-CNS6.json",
|
||||
"$(SRCROOT)/Source/Data/Build/Release/vChewingFactoryDatabase.sqlite",
|
||||
"$(SRCROOT)/Source/Data/Build/Release/vChewingFactoryDatabase.sqlite-journal",
|
||||
"$(SRCROOT)/Source/Data/Build/Release/",
|
||||
"$(SRCROOT)/Source/Data/data-chs.txt",
|
||||
"$(SRCROOT)/Source/Data/data-cht.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
|
|
Loading…
Reference in New Issue