Repo // Add SQLite support for factory database.

This commit is contained in:
ShikiSuen 2023-11-25 18:36:23 +08:00
parent 40d866714e
commit 133901ede2
37 changed files with 750 additions and 843 deletions

View File

@ -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()

View File

@ -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
}

View File

@ -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(

View File

@ -9,6 +9,7 @@
import Foundation
import Megrez
import Shared
import SQLite3
public extension vChewingLM {
/// LMInstantiatorLMI
@ -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
)
// currentCassetteMetadata
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())
}
//

View File

@ -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)
}
}

View File

@ -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": "˙",
]
}
}

View File

@ -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;
"""

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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(

View File

@ -87,7 +87,7 @@ public extension AppDelegate {
)
}
if !PrefMgr.shared.onlyLoadFactoryLangModelsIfNeeded { LMMgr.loadDataModelsOnAppDelegate() }
LMMgr.connectCoreDB()
LMMgr.loadCassetteData()
LMMgr.initUserLangModels()
folderMonitor.folderDidChange = { [weak self] in

View File

@ -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)] = [
("", ""), ("", ""), ("", ""), ("", ""), ("", ""), ("", ""), ("", ""), ("", ""),

View File

@ -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) {

View File

@ -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 }

View File

@ -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 {

View File

@ -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

View File

@ -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)
}

View File

@ -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: "")

View File

@ -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 {

View File

@ -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"
)
)
}
}

View File

@ -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
}
}

View File

@ -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"

View File

@ -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()
}

View File

@ -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 }

View File

@ -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,

View File

@ -334,7 +334,7 @@ class CtlPrefWindow: NSWindowController, NSWindowDelegate {
}
@IBAction func toggledExternalFactoryPlistDataOnOff(_: NSButton) {
LMMgr.reloadFactoryDictionaryFiles()
LMMgr.connectCoreDB()
}
@IBAction func resetSpecifiedUserDataFolder(_: Any) {

View File

@ -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

View File

@ -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"/>

View File

@ -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";

View File

@ -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 キーで確認";

View File

@ -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" = "在逐字选字模式当中使用空格键确认当前高亮候选字";

View File

@ -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" = "在逐字選字模式當中使用空格鍵確認當前高亮候選字";

View File

@ -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;