Files
msime-windows/engine/local_modes/jianpin_query.cpp
T
fanlusky a7779d2b05 refactor(engine): 将 engine 从 submodule 内嵌为仓内一等组件
`vendor/MetasequoiaImeEngine` submodule 改为 `engine/` 目录,导入的是
product-lock.json 原本钉住的提交 c810d201,逐文件核对过 blob,因此这是一次
搬迁而不是引擎升级。引擎自己的嵌套 submodule(googlepinyinime-rev、utfcpp、
miniaudio)按各自 gitlink 一并展开,来源与提交见 engine/UPSTREAM.md。

Windows 专门化裁掉了不属于本产品的部分:whisper.cpp 与本地推理 provider、
macOS 示例宿主、整个 dictionary/ 建库流水线、上游 CI 与 release-please 配置。
词库仍按 product-lock.json 在构建时取发布产物。

引擎不再进 product-lock.json:没有 gitlink 就没有可比对的东西,锁里写一个上游
commit 等于宣称这棵树仍等于上游,而第一个 Windows 专门化改动落地时这句话就假了。
verify-contracts / verify-checkout 随之移除,verify-published 保留。

CI 与工具链:引擎源码并入根 scripts/format.sh(删掉引擎自己的那份,避免两套排除
列表分叉);引擎 voice.yml 里的 silero_vad.onnx 摘要校验移到本仓 lock job;
engine-bump-triage 工作流与其测试删除;gitsubmodule 的 dependabot 频率由 daily
改为 monthly。

README、README.en、AGENTS.md、CONTRIBUTING、docs/product-release.md 及各子目录
文档同步说明新的位置与边界。
2026-09-12 03:40:48 +08:00

239 lines
7.0 KiB
C++

#include "../contracts/assets/assets.h"
#include "jianpin_query.h"
#include "../core/data_path.h"
#include "../quanpin/quanpin_query.h"
#include <sqlite3.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
namespace metasequoia::local_modes
{
namespace
{
struct DatabaseCloser
{
void operator()(sqlite3 *database) const
{
if (database != nullptr)
{
sqlite3_close(database);
}
}
};
struct StatementCloser
{
void operator()(sqlite3_stmt *statement) const
{
if (statement != nullptr)
{
sqlite3_finalize(statement);
}
}
};
using Database = std::unique_ptr<sqlite3, DatabaseCloser>;
using Statement = std::unique_ptr<sqlite3_stmt, StatementCloser>;
std::string normalize_code(const std::string &code)
{
std::string normalized;
normalized.reserve(code.size());
for (unsigned char character : code)
{
if (character >= 'A' && character <= 'Z')
{
character = static_cast<unsigned char>(character - 'A' + 'a');
}
if (character < 'a' || character > 'z')
{
return {};
}
normalized.push_back(static_cast<char>(character));
}
return normalized;
}
std::string decode_shuangpin_initial(char code, const ShuangpinProfile &profile)
{
const std::string mapped_key(1, code);
for (const auto &[initial, key] : profile.initials)
{
if (key == mapped_key)
{
return initial;
}
}
return mapped_key;
}
quanpin::Segments expand_code(const std::string &normalized, SchemeType scheme, const ShuangpinProfile &profile)
{
quanpin::Segments segments;
segments.reserve(normalized.size());
for (const char character : normalized)
{
segments.push_back(scheme == SchemeType::Shuangpin ? decode_shuangpin_initial(character, profile)
: std::string(1, character));
}
return segments;
}
std::vector<std::string> split_key(const std::string &key)
{
std::vector<std::string> parts;
std::size_t start = 0;
while (true)
{
const std::size_t separator = key.find('\'', start);
if (separator == std::string::npos)
{
parts.push_back(key.substr(start));
return parts;
}
parts.push_back(key.substr(start, separator - start));
start = separator + 1;
}
}
std::string syllable_initial(const std::string &syllable)
{
if (syllable.size() >= 2)
{
const std::string prefix = syllable.substr(0, 2);
if (prefix == "zh" || prefix == "ch" || prefix == "sh")
{
return prefix;
}
}
return syllable.empty() ? std::string{} : syllable.substr(0, 1);
}
bool key_matches_initials(const std::string &key, const quanpin::Segments &initials)
{
const auto syllables = split_key(key);
if (syllables.size() != initials.size())
{
return false;
}
for (std::size_t index = 0; index < initials.size(); ++index)
{
if (syllable_initial(syllables[index]) != initials[index])
{
return false;
}
}
return true;
}
int scan_limit(int limit, bool filter_initials)
{
if (!filter_initials || limit > 100000000)
{
return limit;
}
return std::max(limit * 32, 512);
}
LocalQueryResult query_failure(const char *diagnostic)
{
return {{}, std::string(diagnostic)};
}
} // namespace
std::string jianpin_ranking_context(const std::string &code, SchemeType scheme, const ShuangpinProfile &profile)
{
const std::string normalized = normalize_code(code);
return normalized.empty() ? std::string{} : quanpin::join_segments(expand_code(normalized, scheme, profile));
}
LocalQueryResult query_jianpin(const std::string &code, SchemeType scheme, int limit, const ShuangpinProfile &profile)
{
return query_jianpin(code, scheme, data_file_path(metasequoia::assets::main_dictionary), limit, profile);
}
LocalQueryResult query_jianpin(const std::string &code, SchemeType scheme, const std::filesystem::path &database_path,
int limit, const ShuangpinProfile &profile)
{
if (limit <= 0)
{
return {};
}
const std::string normalized = normalize_code(code);
if (normalized.empty())
{
return {};
}
const auto segments = expand_code(normalized, scheme, profile);
const std::string table = quanpin::build_table_name(segments);
if (table.empty())
{
return {};
}
sqlite3 *raw_database = nullptr;
if (database_path.empty() || sqlite3_open_v2(path_to_utf8(database_path).c_str(), &raw_database,
SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX, nullptr) != SQLITE_OK)
{
if (raw_database != nullptr)
{
sqlite3_close(raw_database);
}
return query_failure("Super-jianpin database is unavailable.");
}
Database database(raw_database);
sqlite3_busy_timeout(database.get(), 1000);
const std::string jianpin = quanpin::segments_to_jianpin(segments);
const bool filter_initials = scheme == SchemeType::Shuangpin;
const std::string sql =
"SELECT \"key\",\"value\",\"weight\" FROM \"" + table + "\" WHERE \"jp\"=?1 ORDER BY \"weight\" DESC LIMIT ?2";
sqlite3_stmt *raw_statement = nullptr;
if (sqlite3_prepare_v2(database.get(), sql.c_str(), -1, &raw_statement, nullptr) != SQLITE_OK)
{
return query_failure("Super-jianpin database could not be queried.");
}
Statement statement(raw_statement);
if (sqlite3_bind_text(statement.get(), 1, jianpin.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK ||
sqlite3_bind_int(statement.get(), 2, scan_limit(limit, filter_initials)) != SQLITE_OK)
{
return query_failure("Super-jianpin database could not be queried.");
}
const std::string matched_code = quanpin::join_segments(segments);
LocalQueryResult result;
int step_result = SQLITE_ROW;
while ((step_result = sqlite3_step(statement.get())) == SQLITE_ROW)
{
const auto *key = reinterpret_cast<const char *>(sqlite3_column_text(statement.get(), 0));
const auto *value = reinterpret_cast<const char *>(sqlite3_column_text(statement.get(), 1));
if (value == nullptr)
{
continue;
}
const std::string canonical = key == nullptr ? std::string{} : std::string(key);
if (filter_initials && !key_matches_initials(canonical, segments))
{
continue;
}
result.candidates.emplace_back(matched_code, value, sqlite3_column_int64(statement.get(), 2),
CandidateSource::Database, canonical);
if (static_cast<int>(result.candidates.size()) >= limit)
{
break;
}
}
if (step_result != SQLITE_DONE && step_result != SQLITE_ROW)
{
return query_failure("Super-jianpin database could not be queried.");
}
return result;
}
} // namespace metasequoia::local_modes