Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions src/ConfigTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <fstream>
#include <filesystem>
#include <memory>
#include <set>
#include <string_view>
#include <utility>
#include <vector>
Expand All @@ -33,10 +34,12 @@
#include "ConfigTestBase.hpp"
#include "ConversionChain.hpp"
#include "Converter.hpp"
#include "Dict.hpp"
#include "Segments.hpp"
#include "Exception.hpp"
#include "ResourceProvider.hpp"
#include "TestUtilsUTF8.hpp"
#include "UTF8Util.hpp"

namespace opencc {
namespace {
Expand Down Expand Up @@ -188,6 +191,78 @@ std::string InlineSingleStepConfig(const std::string& segmentationEntries,
"}\n";
}

std::string ChainConfig(const std::vector<std::string>& dicts) {
std::string config =
"{\n"
" \"name\": \"Rime Compatibility Test\",\n"
" \"conversion_chain\": [\n";
for (size_t i = 0; i < dicts.size(); i++) {
config +=
" {\n"
" \"dict\": " +
dicts[i] +
"\n"
" }";
config += (i + 1 == dicts.size()) ? "\n" : ",\n";
}
config +=
" ]\n"
"}\n";
return config;
}

bool ConvertWordLikeRime(const ConversionChainPtr& chain,
const std::string& input,
std::vector<std::string>* converted) {
std::vector<std::string> current{input};
bool hasExactMatch = false;

const auto AddIfNew = [](const std::string& value,
std::vector<std::string>* values,
std::set<std::string>* seen) {
if (seen->insert(value).second) {
values->push_back(value);
}
};

for (const ConversionPtr& conversion : chain->GetConversions()) {
const DictPtr dict = conversion->GetDict();
std::vector<std::string> next;
std::set<std::string> seen;

for (const std::string& word : current) {
const Optional<const DictEntry*> exact = dict->Match(word);
if (!exact.IsNull()) {
hasExactMatch = true;
for (const std::string& value : exact.Get()->Values()) {
AddIfNew(value, &next, &seen);
}
continue;
}

std::string fallback;
for (const char* pstr = word.c_str(); *pstr != '\0';) {
const Optional<const DictEntry*> prefix = dict->MatchPrefix(pstr);
size_t matchedLength;
if (prefix.IsNull()) {
matchedLength = UTF8Util::NextCharLength(pstr);
fallback.append(pstr, matchedLength);
} else {
matchedLength = prefix.Get()->KeyLength();
fallback += prefix.Get()->GetDefault();
}
pstr += matchedLength;
}
AddIfNew(fallback, &next, &seen);
}

current = std::move(next);
}

*converted = current;
return hasExactMatch;
}

std::string FindOcd2DictionaryDir(const std::string& configTestDirPath) {
std::vector<fs::path> candidates;
if (!PACKAGE_DATA_DIRECTORY.empty()) {
Expand Down Expand Up @@ -1060,10 +1135,79 @@ TEST_F(ConfigTest, NormalizationGetConversionChainIsNonNull) {
const ConverterPtr conv = c.NewFromString(config, CONFIG_TEST_DIR_PATH);
const ConversionChainPtr chain = conv->GetConversionChain();
ASSERT_NE(nullptr, chain);
const std::list<ConversionPtr> conversions = chain->GetConversions();
ASSERT_FALSE(conversions.empty());
ASSERT_NE(nullptr, conversions.front()->GetDict());
EXPECT_FALSE(conversions.front()->GetDict()->Match(utf8("乙")).IsNull());
EXPECT_TRUE(conversions.front()->GetDict()->Match(utf8("甲")).IsNull());

// Verify it is the main chain: 乙 → 丙 (not the normalization chain 甲 → 乙).
const SegmentsPtr result = chain->Convert(SegmentsPtr(new Segments{utf8("乙")}));
ASSERT_EQ(1u, result->Length());
EXPECT_EQ(utf8("丙"), result->At(0));
}

TEST_F(ConfigTest, RimeConvertWordExactExpansionThreadsThroughChain) {
const fs::path tempDir = MakeTempDir("opencc-rime-exact-expansion-test");
WriteFile(tempDir / "first.txt", utf8("里\t裏 里 哩\n"));

const std::string config = ChainConfig({
"{\"type\": \"text\", \"file\": \"first.txt\"}",
"{\n"
" \"type\": \"inline\",\n"
" \"entries\": {\"裏\": \"裡\"}\n"
" }",
});
Config c;
const ConverterPtr conv = c.NewFromString(config, PathString(tempDir));

std::vector<std::string> converted;
EXPECT_TRUE(ConvertWordLikeRime(conv->GetConversionChain(), utf8("里"),
&converted));
EXPECT_EQ((std::vector<std::string>{utf8("裡"), utf8("里"), utf8("哩")}),
converted);

fs::remove_all(tempDir);
}

TEST_F(ConfigTest, RimeConvertWordPartialFallbackContinuesToNextStage) {
const std::string config = ChainConfig({
"{\n"
" \"type\": \"inline\",\n"
" \"entries\": {\"内\": \"內\"}\n"
" }",
"{\n"
" \"type\": \"inline\",\n"
" \"entries\": {\"內存\": \"記憶體\"}\n"
" }",
});
Config c;
const ConverterPtr conv = c.NewFromString(config, CONFIG_TEST_DIR_PATH);

std::vector<std::string> converted;
EXPECT_TRUE(ConvertWordLikeRime(conv->GetConversionChain(), utf8("内存"),
&converted));
EXPECT_EQ((std::vector<std::string>{utf8("記憶體")}), converted);
}

TEST_F(ConfigTest, RimeConvertWordReturnsFalseWithoutAnyExactMatch) {
const std::string config = ChainConfig({
"{\n"
" \"type\": \"inline\",\n"
" \"entries\": {\"a\": \"A\"}\n"
" }",
"{\n"
" \"type\": \"inline\",\n"
" \"entries\": {\"z\": \"Z\"}\n"
" }",
});
Config c;
const ConverterPtr conv = c.NewFromString(config, CONFIG_TEST_DIR_PATH);

std::vector<std::string> converted;
EXPECT_FALSE(ConvertWordLikeRime(conv->GetConversionChain(), "abc",
&converted));
EXPECT_EQ((std::vector<std::string>{"Abc"}), converted);
}

} // namespace opencc
Loading