diff --git a/Sources/Hotenka/HotenkaChineseConverter.swift b/Sources/Hotenka/HotenkaChineseConverter.swift index 71b9aaf..6f89fd4 100644 --- a/Sources/Hotenka/HotenkaChineseConverter.swift +++ b/Sources/Hotenka/HotenkaChineseConverter.swift @@ -24,6 +24,7 @@ */ import Foundation +import SQLite3 #if os(Linux) import Glibc @@ -43,6 +44,25 @@ public enum DictType { 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 +157,42 @@ public class HotenkaChineseConverter { // MARK: - Public Methods - public func convert(_ target: String, to dictType: DictType) -> String { - var dictTypeKey: String - + private static func dictTypeKey(_ dictType: DictType) -> String { switch dictType { case .zhHantTW: - dictTypeKey = "zh2TW" + return "zh2TW" case .zhHantHK: - dictTypeKey = "zh2HK" + return "zh2HK" case .zhHansSG: - dictTypeKey = "zh2SG" + return "zh2SG" case .zhHansJP: - dictTypeKey = "zh2JP" + return "zh2JP" case .zhHantKX: - dictTypeKey = "zh2KX" + return "zh2KX" case .zhHansCN: - dictTypeKey = "zh2CN" + return "zh2CN" } + } + + public func query(dict dictTypeKey: String, key searchKey: String) -> String? { + guard ptrSQL != nil else { return dict[dictTypeKey]?[searchKey] } + let sqlQuery = "SELECT * FROM DATA_HOTENKA WHERE dict='\(dictTypeKey)' AND theKey='\(searchKey)';" + sqlite3_prepare_v2(ptrSQL, (sqlQuery as NSString).utf8String, -1, &ptrStatement, nil) + // 此處只需要用到第一筆結果。 + while sqlite3_step(ptrStatement) == SQLITE_ROW { + guard let rawValue = sqlite3_column_text(ptrStatement, 3) else { continue } + return String(cString: rawValue) + } + return nil + } + + public func convert(_ target: String, to dictType: DictType) -> String { + let dictTypeKey: String = Self.dictTypeKey(dictType) var result = "" - guard let useDict = dict[dictTypeKey] else { return target } + if ptrSQL == nil { + guard dict[dictTypeKey] != nil else { return target } + } var i = 0 while i < (target.count) { @@ -167,7 +203,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: dictTypeKey, key: String(target[start ..< end])) else { j -= 1 continue } diff --git a/Tests/HotenkaTests/HotenkaTests_SQLite.swift b/Tests/HotenkaTests/HotenkaTests_SQLite.swift new file mode 100644 index 0000000..45a5c4a --- /dev/null +++ b/Tests/HotenkaTests/HotenkaTests_SQLite.swift @@ -0,0 +1,87 @@ +// Swiftified by (c) 2022 and onwards The vChewing Project (MIT-NTL License). +// Rebranded from (c) Nick Chen's Obj-C library "NCChineseConverter" (MIT License). +/* + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. 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 above. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import SQLite3 +import XCTest + +@testable import Hotenka + +private let packageRootPath = URL(fileURLWithPath: #file).pathComponents.prefix(while: { $0 != "Tests" }).joined( + separator: "/" +).dropFirst() + +private let testDataPath: String = packageRootPath + "/Tests/TestDictData/" + +extension HotenkaTests { + func testGeneratingSQLiteDB() throws { + NSLog("// Start loading from: \(packageRootPath)") + let testInstance: HotenkaChineseConverter = .init(dictDir: testDataPath) + NSLog("// Loading complete. Generating SQLite database.") + var ptrSQL: OpaquePointer? + let dbPath = testDataPath + "convdict.sqlite" + + XCTAssertTrue( + sqlite3_open(dbPath, &ptrSQL) == SQLITE_OK, + "HOTENKA: SQLite Database Initialization Error." + ) + + let sqlMakeTableHotenka = """ + DROP TABLE IF EXISTS DATA_HOTENKA; + CREATE TABLE IF NOT EXISTS DATA_HOTENKA ( id INTEGER PRIMARY KEY AUTOINCREMENT, dict TEXT, theKey TEXT, theValue TEXT); + """ + + XCTAssertTrue( + sqlite3_exec(ptrSQL, (sqlMakeTableHotenka as NSString).utf8String, nil, nil, nil) == SQLITE_OK, + "HOTENKA: SQLite Table Creation Failed." + ) + + testInstance.dict.forEach { dictName, subDict in + subDict.forEach { key, value in + let sqlInsertion = "INSERT INTO DATA_HOTENKA (dict, theKey, theValue) VALUES ('\(dictName)', '\(key)', '\(value)')" + assert( + sqlite3_exec(ptrSQL, (sqlInsertion as NSString).utf8String, nil, nil, nil) == SQLITE_OK, + "HOTENKA: Failed: \(sqlInsertion)" + ) + } + } + sqlite3_close_v2(ptrSQL) + } + + func testSampleWithSQLiteDB() throws { + NSLog("// Start loading plist from: \(packageRootPath)") + let testInstance2: HotenkaChineseConverter = .init(sqliteDir: testDataPath + "convdict.sqlite") + NSLog("// Successfully loading sql dictionary.") + + let oriString = "为中华崛起而读书" + let result1 = testInstance2.convert(oriString, to: .zhHantTW) + let result2 = testInstance2.convert(result1, to: .zhHantKX) + let result3 = testInstance2.convert(result2, to: .zhHansJP) + NSLog("// Results: \(result1) \(result2) \(result3)") + XCTAssertEqual(result1, "為中華崛起而讀書") + XCTAssertEqual(result2, "爲中華崛起而讀書") + XCTAssertEqual(result3, "為中華崛起而読書") + } +} diff --git a/Tests/TestDictData/convdict.sqlite b/Tests/TestDictData/convdict.sqlite new file mode 100644 index 0000000..7feb498 Binary files /dev/null and b/Tests/TestDictData/convdict.sqlite differ