#include "settings/dictionary_manager.h" #include "config/ime_config.h" #include "utils/common_utils.h" #include "MetasequoiaImeEngine/common/helpcode_utils.h" #include "MetasequoiaImeEngine/quanpin/quanpin_query.h" #include "MetasequoiaImeEngine/quanpin/quanpin_utils.h" #include #include #include #include #include namespace SettingsDictionary { namespace json = boost::json; namespace { struct DbCloser { void operator()(sqlite3 *db) const { if (db) sqlite3_close(db); } }; using Db = std::unique_ptr; struct StmtCloser { void operator()(sqlite3_stmt *stmt) const { if (stmt) sqlite3_finalize(stmt); } }; using Stmt = std::unique_ptr; json::object Result(bool ok, std::string message) { return {{"ok", ok}, {"message", std::move(message)}, {"rows", json::array{}}}; } std::string StringValue(const json::object &obj, const char *key) { const auto *value = obj.if_contains(key); return value && value->is_string() ? std::string(value->as_string()) : std::string{}; } int IntValue(const json::object &obj, const char *key, int fallback) { const auto *value = obj.if_contains(key); return value && value->is_int64() ? static_cast(value->as_int64()) : fallback; } bool IsAsciiWord(const std::string &value) { return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char ch) { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '-' || ch == '\''; }); } Db OpenDatabase(const std::string &name, std::string &error) { sqlite3 *raw = nullptr; const std::string path = CommonUtils::get_ime_data_path() + "\\" + name; if (sqlite3_open_v2(path.c_str(), &raw, SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK) { error = raw ? sqlite3_errmsg(raw) : "无法打开数据库"; if (raw) sqlite3_close(raw); return {}; } sqlite3_busy_timeout(raw, 3000); return Db(raw); } Stmt Prepare(sqlite3 *db, const std::string &sql, std::string &error) { sqlite3_stmt *raw = nullptr; if (sqlite3_prepare_v2(db, sql.c_str(), -1, &raw, nullptr) != SQLITE_OK) { error = sqlite3_errmsg(db); return {}; } return Stmt(raw); } bool BindText(sqlite3_stmt *stmt, int index, const std::string &value) { return sqlite3_bind_text(stmt, index, value.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK; } bool NormalizePinyin(const std::string &mode, const std::string &input, quanpin::Segments &segments, std::string &normalized, std::string &message) { std::string source = input; source.erase(std::remove_if(source.begin(), source.end(), [](unsigned char ch) { return std::isspace(ch); }), source.end()); std::transform(source.begin(), source.end(), source.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); source.erase(std::remove(source.begin(), source.end(), '\''), source.end()); const auto cuts = quanpin::cut_pinyin_by_mode(source, "correction"); std::string joined = cuts.empty() ? std::string{} : quanpin::join_segments(cuts.front()); std::string joined_without_delimiters = joined; joined_without_delimiters.erase(std::remove(joined_without_delimiters.begin(), joined_without_delimiters.end(), '\''), joined_without_delimiters.end()); if (cuts.empty() || cuts.front().empty() || joined_without_delimiters != source) { message = "全拼拼音不合法"; return false; } segments = cuts.front(); normalized = std::move(joined); return true; } bool ValidateChineseEntry(const std::string &mode, const std::string &code, const std::string &word, quanpin::Segments &segments, std::string &normalized, std::string &message) { if (word.empty()) { message = "词条不能为空"; return false; } if (!NormalizePinyin(mode, code, segments, normalized, message)) return false; const size_t han_count = HelpcodeUtils::count_han_chars(word); if (han_count == 0 || han_count != segments.size()) { message = "拼音音节数量必须与汉字数量一致"; return false; } return true; } json::object QueryChinese(const std::string &mode, const std::string &search) { std::string error; Db db = OpenDatabase("msime.db", error); if (!db) return Result(false, "打开拼音词库失败:" + error); quanpin::Segments segments; std::string normalized; if (!search.empty() && !NormalizePinyin(mode, search, segments, normalized, error)) return Result(false, error); json::array rows; if (!normalized.empty()) { const std::string table = quanpin::build_table_name(segments); Stmt stmt = Prepare(db.get(), "SELECT key,value,weight FROM \"" + table + "\" WHERE key=?1 ORDER BY weight DESC", error); if (!stmt || !BindText(stmt.get(), 1, normalized)) return Result(false, "查询失败:" + error); while (sqlite3_step(stmt.get()) == SQLITE_ROW) { rows.push_back({{"code", reinterpret_cast(sqlite3_column_text(stmt.get(), 0))}, {"word", reinterpret_cast(sqlite3_column_text(stmt.get(), 1))}, {"weight", sqlite3_column_int(stmt.get(), 2)}}); } } json::object result = Result(true, rows.empty() ? "没有找到词条" : "查询成功"); result["rows"] = std::move(rows); return result; } bool ChineseExists(sqlite3 *db, const quanpin::Segments &segments, const std::string &key, const std::string &word) { std::string error; Stmt stmt = Prepare(db, "SELECT 1 FROM \"" + quanpin::build_table_name(segments) + "\" WHERE key=?1 AND value=?2 LIMIT 1", error); if (!stmt || !BindText(stmt.get(), 1, key) || !BindText(stmt.get(), 2, word)) return false; return sqlite3_step(stmt.get()) == SQLITE_ROW; } json::object MutateChinese(const json::object &request) { const std::string mode = StringValue(request, "dictionary"); const std::string action = StringValue(request, "action"); const std::string code = StringValue(request, "code"); const std::string word = StringValue(request, "word"); const int weight = (std::max)(0, IntValue(request, "weight", 10000)); quanpin::Segments segments; std::string key, error; if (!ValidateChineseEntry(mode, code, word, segments, key, error)) return Result(false, error); Db db = OpenDatabase("msime.db", error); if (!db) return Result(false, "打开拼音词库失败:" + error); const std::string table = quanpin::build_table_name(segments); if (action == "create") { if (ChineseExists(db.get(), segments, key, word)) return Result(false, "词条已经存在"); Stmt stmt = Prepare(db.get(), "INSERT INTO \"" + table + "\"(key,jp,value,weight) VALUES(?1,?2,?3,?4)", error); if (!stmt || !BindText(stmt.get(), 1, key) || !BindText(stmt.get(), 2, quanpin::segments_to_jianpin(segments)) || !BindText(stmt.get(), 3, word) || sqlite3_bind_int(stmt.get(), 4, weight) != SQLITE_OK || sqlite3_step(stmt.get()) != SQLITE_DONE) return Result(false, "新增失败:" + std::string(sqlite3_errmsg(db.get()))); return Result(true, "词条新增成功"); } const std::string old_code = StringValue(request, "oldCode"); const std::string old_word = StringValue(request, "oldWord"); quanpin::Segments old_segments; std::string old_key; if (!ValidateChineseEntry(mode, old_code, old_word, old_segments, old_key, error)) return Result(false, "原词条无效:" + error); const std::string old_table = quanpin::build_table_name(old_segments); if (!ChineseExists(db.get(), old_segments, old_key, old_word)) return Result(false, "原词条不存在或已被删除"); if (action == "delete") { Stmt stmt = Prepare(db.get(), "DELETE FROM \"" + old_table + "\" WHERE key=?1 AND value=?2", error); if (!stmt || !BindText(stmt.get(), 1, old_key) || !BindText(stmt.get(), 2, old_word) || sqlite3_step(stmt.get()) != SQLITE_DONE) return Result(false, "删除失败:" + std::string(sqlite3_errmsg(db.get()))); return Result(true, "词条删除成功"); } if (action != "update") return Result(false, "未知操作"); if ((old_key != key || old_word != word) && ChineseExists(db.get(), segments, key, word)) return Result(false, "修改后的词条已经存在"); sqlite3_exec(db.get(), "BEGIN IMMEDIATE", nullptr, nullptr, nullptr); Stmt remove = Prepare(db.get(), "DELETE FROM \"" + old_table + "\" WHERE key=?1 AND value=?2", error); Stmt insert = Prepare(db.get(), "INSERT INTO \"" + table + "\"(key,jp,value,weight) VALUES(?1,?2,?3,?4)", error); const bool ok = remove && insert && BindText(remove.get(), 1, old_key) && BindText(remove.get(), 2, old_word) && sqlite3_step(remove.get()) == SQLITE_DONE && BindText(insert.get(), 1, key) && BindText(insert.get(), 2, quanpin::segments_to_jianpin(segments)) && BindText(insert.get(), 3, word) && sqlite3_bind_int(insert.get(), 4, weight) == SQLITE_OK && sqlite3_step(insert.get()) == SQLITE_DONE; sqlite3_exec(db.get(), ok ? "COMMIT" : "ROLLBACK", nullptr, nullptr, nullptr); return Result(ok, ok ? "词条修改成功" : "修改失败:" + std::string(sqlite3_errmsg(db.get()))); } json::object HandleEnglish(const json::object &request) { const std::string action = StringValue(request, "action"); std::string word = StringValue(request, "word"); const std::string display = StringValue(request, "display"); std::transform(word.begin(), word.end(), word.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); std::string error; Db db = OpenDatabase("english.db", error); if (!db) return Result(false, "打开英文词库失败:" + error); if (action == "query") { Stmt stmt = Prepare(db.get(), "SELECT word,display FROM english_words WHERE word LIKE ?1 ORDER BY word", error); const std::string pattern = word + "%"; if (!stmt || !BindText(stmt.get(), 1, pattern)) return Result(false, "查询失败"); json::array rows; while (sqlite3_step(stmt.get()) == SQLITE_ROW) rows.push_back({{"word", reinterpret_cast(sqlite3_column_text(stmt.get(), 0))}, {"display", reinterpret_cast(sqlite3_column_text(stmt.get(), 1))}}); json::object result = Result(true, rows.empty() ? "没有找到词条" : "查询成功"); result["rows"] = std::move(rows); return result; } if (!IsAsciiWord(word) || display.empty()) return Result(false, "英文单词仅支持英文字母、连字符和撇号,显示内容不能为空"); const std::string old_word = StringValue(request, "oldWord"); std::string sql; if (action == "create") sql = "INSERT INTO english_words(word,display) VALUES(?1,?2)"; else if (action == "update") sql = "UPDATE english_words SET word=?1,display=?2 WHERE word=?3"; else if (action == "delete") sql = "DELETE FROM english_words WHERE word=?1"; else return Result(false, "未知操作"); Stmt stmt = Prepare(db.get(), sql, error); bool ok = stmt && BindText(stmt.get(), 1, action == "delete" ? old_word : word); if (ok && action != "delete") ok = BindText(stmt.get(), 2, display); if (ok && action == "update") ok = BindText(stmt.get(), 3, old_word); ok = ok && sqlite3_step(stmt.get()) == SQLITE_DONE && sqlite3_changes(db.get()) > 0; const char *label = action == "create" ? "新增" : action == "update" ? "修改" : "删除"; return Result(ok, ok ? std::string("英文词条") + label + "成功" : std::string(label) + "失败:" + sqlite3_errmsg(db.get())); } json::object HandleWubi(const json::object &request) { const std::string action = StringValue(request, "action"); std::string code = StringValue(request, "code"); const std::string word = StringValue(request, "word"); const int weight = (std::max)(0, IntValue(request, "weight", 10000)); std::transform(code.begin(), code.end(), code.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); std::string error; Db db = OpenDatabase("msime.db", error); if (!db) return Result(false, "打开五笔词库失败:" + error); if (action == "query") { if (code.empty() || !std::all_of(code.begin(), code.end(), [](unsigned char ch) { return ch >= 'a' && ch <= 'z'; })) return Result(false, "请输入合法的五笔编码"); Stmt stmt = Prepare(db.get(), "SELECT key,value,weight FROM wubi86 WHERE key LIKE ?1 ORDER BY weight DESC", error); if (!stmt || !BindText(stmt.get(), 1, code + "%")) return Result(false, "查询失败:" + error); json::array rows; while (sqlite3_step(stmt.get()) == SQLITE_ROW) rows.push_back({{"code", reinterpret_cast(sqlite3_column_text(stmt.get(), 0))}, {"word", reinterpret_cast(sqlite3_column_text(stmt.get(), 1))}, {"weight", sqlite3_column_int(stmt.get(), 2)}}); json::object result = Result(true, rows.empty() ? "没有找到词条" : "查询成功"); result["rows"] = std::move(rows); return result; } if (code.empty() || word.empty() || !std::all_of(code.begin(), code.end(), [](unsigned char ch) { return ch >= 'a' && ch <= 'z'; })) return Result(false, "五笔编码和词条不能为空,编码只能包含英文字母"); const std::string old_code = StringValue(request, "oldCode"); const std::string old_word = StringValue(request, "oldWord"); std::string sql; if (action == "create") sql = "INSERT INTO wubi86(key,value,weight) VALUES(?1,?2,?3)"; else if (action == "update") sql = "UPDATE wubi86 SET key=?1,value=?2,weight=?3 WHERE key=?4 AND value=?5"; else if (action == "delete") sql = "DELETE FROM wubi86 WHERE key=?1 AND value=?2"; else return Result(false, "未知操作"); Stmt stmt = Prepare(db.get(), sql, error); bool ok = stmt != nullptr; if (ok && action == "delete") ok = BindText(stmt.get(), 1, old_code) && BindText(stmt.get(), 2, old_word); else if (ok) { ok = BindText(stmt.get(), 1, code) && BindText(stmt.get(), 2, word) && sqlite3_bind_int(stmt.get(), 3, weight) == SQLITE_OK; if (ok && action == "update") ok = BindText(stmt.get(), 4, old_code) && BindText(stmt.get(), 5, old_word); } ok = ok && sqlite3_step(stmt.get()) == SQLITE_DONE && sqlite3_changes(db.get()) > 0; const char *label = action == "create" ? "新增" : action == "update" ? "修改" : "删除"; return Result(ok, ok ? std::string("五笔词条") + label + "成功" : std::string(label) + "失败:" + sqlite3_errmsg(db.get())); } json::object HandleQuickPhrase(const json::object &request) { const std::string action = StringValue(request, "action"); std::string code = StringValue(request, "code"); const std::string phrase = StringValue(request, "word"); const int weight = (std::max)(0, IntValue(request, "weight", 10)); std::transform(code.begin(), code.end(), code.begin(), [](unsigned char ch) { return static_cast(std::tolower(ch)); }); const auto valid_code = [](const std::string &value) { return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char ch) { return ch >= 'a' && ch <= 'z'; }); }; std::string error; Db db = OpenDatabase("msime.db", error); if (!db) return Result(false, "打开快捷短语表失败:" + error); if (action == "query") { if (!code.empty() && !valid_code(code)) return Result(false, "编码只能包含英文字母"); Stmt stmt = Prepare(db.get(), "SELECT key,value,weight FROM quick_parases WHERE key LIKE ?1 " "ORDER BY weight DESC,key,value", error); if (!stmt || !BindText(stmt.get(), 1, code + "%")) return Result(false, "查询失败:" + error); json::array rows; while (sqlite3_step(stmt.get()) == SQLITE_ROW) rows.push_back({{"code", reinterpret_cast(sqlite3_column_text(stmt.get(), 0))}, {"word", reinterpret_cast(sqlite3_column_text(stmt.get(), 1))}, {"weight", sqlite3_column_int(stmt.get(), 2)}}); json::object result = Result(true, rows.empty() ? "没有找到快捷短语" : "查询成功"); result["rows"] = std::move(rows); return result; } if (!valid_code(code) || phrase.empty()) return Result(false, "编码只能包含英文字母,短语不能为空"); const std::string old_code = StringValue(request, "oldCode"); const std::string old_phrase = StringValue(request, "oldWord"); std::string sql; if (action == "create") sql = "INSERT INTO quick_parases(key,value,weight) VALUES(?1,?2,?3)"; else if (action == "update") sql = "UPDATE quick_parases SET key=?1,value=?2,weight=?3 WHERE key=?4 AND value=?5"; else if (action == "delete") sql = "DELETE FROM quick_parases WHERE key=?1 AND value=?2"; else return Result(false, "未知操作"); Stmt stmt = Prepare(db.get(), sql, error); bool ok = stmt != nullptr; if (ok && action == "delete") ok = BindText(stmt.get(), 1, old_code) && BindText(stmt.get(), 2, old_phrase); else if (ok) { ok = BindText(stmt.get(), 1, code) && BindText(stmt.get(), 2, phrase) && sqlite3_bind_int(stmt.get(), 3, weight) == SQLITE_OK; if (ok && action == "update") ok = BindText(stmt.get(), 4, old_code) && BindText(stmt.get(), 5, old_phrase); } ok = ok && sqlite3_step(stmt.get()) == SQLITE_DONE && sqlite3_changes(db.get()) > 0; const char *label = action == "create" ? "新增" : action == "update" ? "修改" : "删除"; return Result(ok, ok ? std::string("快捷短语") + label + "成功" : std::string(label) + "失败:" + sqlite3_errmsg(db.get())); } } json::object HandleRequest(const json::object &request) { const std::string dictionary = StringValue(request, "dictionary"); const std::string action = StringValue(request, "action"); if (dictionary == "english") return HandleEnglish(request); if (dictionary == "wubi") return HandleWubi(request); if (dictionary == "quick") return HandleQuickPhrase(request); if (dictionary != "quanpin") return Result(false, "未知词库"); if (action == "query") return QueryChinese(dictionary, StringValue(request, "code")); return MutateChinese(request); } }