#include "../contracts/assets/assets.h" #include "emoji_query.h" #include "../core/data_path.h" #include "../shuangpin/shuangpin_query.h" #include #include #include #include #include #include #include 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; using Statement = std::unique_ptr; bool valid_code(const std::string &code) { return !code.empty() && std::all_of(code.begin(), code.end(), [](unsigned char character) { return (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '\''; }); } std::string lower_ascii(std::string text) { std::transform(text.begin(), text.end(), text.begin(), [](unsigned char character) { return static_cast(std::tolower(character)); }); return text; } LocalQueryResult query_failure(const char *diagnostic) { return {{}, std::string(diagnostic)}; } } // namespace LocalQueryResult query_emoji(const std::string &code, SchemeType scheme, int limit, const ShuangpinProfile &profile) { return query_emoji(code, scheme, data_file_path(metasequoia::assets::other_dictionary), limit, profile); } LocalQueryResult query_emoji(const std::string &code, SchemeType scheme, const std::filesystem::path &database_path, int limit, const ShuangpinProfile &profile) { if (!valid_code(code) || limit <= 0) { return {}; } if (database_path.empty()) { return query_failure("Emoji database is unavailable."); } const std::string lower = lower_ascii(code); std::vector prefixes{lower}; if (scheme == SchemeType::Shuangpin) { const std::string quanpin = shuangpin::normalize_input(lower, profile); if (!quanpin.empty() && quanpin != lower) { prefixes.push_back(quanpin); } } sqlite3 *raw_database = nullptr; if (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("Emoji database is unavailable."); } Database database(raw_database); sqlite3_busy_timeout(database.get(), 1000); struct Entry { std::string text; int sort_order; }; std::vector entries; std::unordered_set seen; constexpr const char *kSql = "SELECT emoji,sort_order FROM emoji_pinyin WHERE key>=?1 AND key(0x7f)); if (sqlite3_bind_text(statement.get(), 1, prefix.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement.get(), 2, upper_bound.c_str(), -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_int(statement.get(), 3, limit) != SQLITE_OK) { return query_failure("Emoji database could not be queried."); } int step_result = SQLITE_ROW; while ((step_result = sqlite3_step(statement.get())) == SQLITE_ROW) { const auto *text = reinterpret_cast(sqlite3_column_text(statement.get(), 0)); if (text != nullptr && seen.insert(text).second) { entries.push_back({text, sqlite3_column_int(statement.get(), 1)}); } } if (step_result != SQLITE_DONE) { return query_failure("Emoji database could not be queried."); } } std::stable_sort(entries.begin(), entries.end(), [](const Entry &left, const Entry &right) { return left.sort_order < right.sort_order; }); const std::size_t count = std::min(static_cast(limit), entries.size()); LocalQueryResult result; result.candidates.reserve(count); for (std::size_t index = 0; index < count; ++index) { result.candidates.emplace_back(lower, entries[index].text, static_cast(count - index), CandidateSource::Emoji); } return result; } } // namespace metasequoia::local_modes