#include "gtest/gtest.h"
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "src/UTF8Util.hpp"
#include "tools/cpp/runfiles/runfiles.h"
using bazel::tools::cpp::runfiles::Runfiles;
namespace opencc {
namespace {
std::vector<std::string> SplitValues(const std::string& values) {
std::vector<std::string> split;
size_t start = 0;
while (start <= values.size()) {
const size_t end = values.find(' ', start);
split.push_back(values.substr(start, end - start));
if (end == std::string::npos) {
break;
}
start = end + 1;
}
return split;
}
bool IsDictionaryEntry(const std::string& line) {
return !line.empty() && line[0] != '#';
}
std::vector<std::unordered_set<std::string>> LoadMultiVariantSets(
const std::string& path) {
std::ifstream stream(path);
EXPECT_TRUE(stream.is_open()) << path;
std::vector<std::unordered_set<std::string>> variantSets;
std::string line;
while (std::getline(stream, line)) {
if (!IsDictionaryEntry(line)) {
continue;
}
const size_t firstTab = line.find('\t');
if (firstTab == std::string::npos) {
continue;
}
const size_t secondTab = line.find('\t', firstTab + 1);
const std::string variantsText =
line.substr(firstTab + 1, secondTab - firstTab - 1);
std::vector<std::string> variants = SplitValues(variantsText);
if (variants.size() <= 1) {
continue;
}
variantSets.emplace_back(variants.begin(), variants.end());
}
return variantSets;
}
bool IsInSameMultiVariantSet(
const std::vector<std::unordered_set<std::string>>& variantSets,
const std::string& first, const std::string& second) {
for (const auto& variantSet : variantSets) {
if (variantSet.count(first) != 0 && variantSet.count(second) != 0) {
return true;
}
}
return false;
}
std::unordered_map<std::string, std::string> LoadPreferredValues(
const std::string& path) {
std::ifstream stream(path);
EXPECT_TRUE(stream.is_open()) << path;
std::unordered_map<std::string, std::string> preferredValues;
std::string line;
while (std::getline(stream, line)) {
if (!IsDictionaryEntry(line)) {
continue;
}
const size_t tab = line.find('\t');
if (tab == std::string::npos) {
ADD_FAILURE() << line;
continue;
}
const std::string key = line.substr(0, tab);
const std::vector<std::string> values = SplitValues(line.substr(tab + 1));
if (!values.empty()) {
preferredValues[key] = values.front();
}
}
return preferredValues;
}
std::unordered_set<std::string> LoadReverseExceptionCharacters(
const std::string& variantsPath, const std::string& variantsRevPath,
const std::string& stCharactersPath) {
const std::vector<std::unordered_set<std::string>> variantSets =
LoadMultiVariantSets(stCharactersPath);
EXPECT_FALSE(variantSets.empty());
const std::unordered_map<std::string, std::string> reversePreferredValues =
LoadPreferredValues(variantsRevPath);
std::ifstream stream(variantsPath);
EXPECT_TRUE(stream.is_open()) << variantsPath;
std::unordered_set<std::string> exceptionChars;
std::string line;
while (std::getline(stream, line)) {
if (!IsDictionaryEntry(line)) {
continue;
}
const size_t tab = line.find('\t');
if (tab == std::string::npos) {
ADD_FAILURE() << line;
continue;
}
const std::string key = line.substr(0, tab);
const std::string values = line.substr(tab + 1);
for (const std::string& value : SplitValues(values)) {
if (key == value) {
continue;
}
const auto reversePreferredValue = reversePreferredValues.find(value);
if (reversePreferredValue != reversePreferredValues.end() &&
reversePreferredValue->second == value) {
continue;
}
if (IsInSameMultiVariantSet(variantSets, key, value)) {
exceptionChars.insert(value);
}
}
}
return exceptionChars;
}
bool ContainsAny(const std::string& text,
const std::unordered_set<std::string>& needles) {
for (const std::string& needle : needles) {
if (text.find(needle) != std::string::npos) {
return true;
}
}
return false;
}
std::unordered_set<std::string> LoadExpectedPhraseExceptions(
const std::string& stPhrasesPath,
const std::unordered_set<std::string>& exceptionChars) {
std::ifstream stream(stPhrasesPath);
EXPECT_TRUE(stream.is_open()) << stPhrasesPath;
std::unordered_set<std::string> expected;
std::string line;
while (std::getline(stream, line)) {
if (!IsDictionaryEntry(line)) {
continue;
}
const size_t tab = line.find('\t');
if (tab == std::string::npos) {
ADD_FAILURE() << line;
continue;
}
const std::string values = line.substr(tab + 1);
const std::vector<std::string> valueList = SplitValues(values);
if (valueList.empty()) {
continue;
}
const std::string& preferredValue = valueList.front();
if (UTF8Util::Length(preferredValue.c_str()) >= 2 &&
ContainsAny(preferredValue, exceptionChars)) {
expected.insert(preferredValue);
}
}
return expected;
}
std::unordered_set<std::string> LoadIdentityPhraseKeys(
const std::string& phrasesPath) {
std::ifstream stream(phrasesPath);
EXPECT_TRUE(stream.is_open()) << phrasesPath;
std::unordered_set<std::string> identityKeys;
std::string line;
while (std::getline(stream, line)) {
if (!IsDictionaryEntry(line)) {
continue;
}
const size_t tab = line.find('\t');
if (tab == std::string::npos) {
ADD_FAILURE() << line;
continue;
}
const std::string key = line.substr(0, tab);
const std::string values = line.substr(tab + 1);
for (const std::string& value : SplitValues(values)) {
if (key == value) {
identityKeys.insert(key);
break;
}
}
}
return identityKeys;
}
void ExpectRevPhrasesComplete(const std::string& variantsName) {
std::unique_ptr<Runfiles> runfiles(Runfiles::CreateForTest());
ASSERT_NE(nullptr, runfiles);
const std::string revPhrasesName = variantsName + "RevPhrases.txt";
const std::string variantsPath =
runfiles->Rlocation("_main/data/dictionary/" + variantsName + ".txt");
const std::string variantsRevPath =
runfiles->Rlocation("_main/data/dictionary/" + variantsName + "Rev.txt");
const std::string phrasesPath = runfiles->Rlocation(
"_main/data/dictionary/" + revPhrasesName);
const std::string stCharactersPath =
runfiles->Rlocation("_main/data/dictionary/STCharacters.txt");
const std::string stPhrasesPath =
runfiles->Rlocation("_main/data/dictionary/STPhrases.txt");
const std::unordered_set<std::string> exceptionChars =
LoadReverseExceptionCharacters(variantsPath, variantsRevPath,
stCharactersPath);
ASSERT_FALSE(exceptionChars.empty()) << variantsName;
const std::unordered_set<std::string> expected =
LoadExpectedPhraseExceptions(stPhrasesPath, exceptionChars);
const std::unordered_set<std::string> identityKeys =
LoadIdentityPhraseKeys(phrasesPath);
std::ostringstream missing;
for (const std::string& phrase : expected) {
if (identityKeys.count(phrase) == 0) {
missing << phrase << "\n";
}
}
EXPECT_TRUE(missing.str().empty())
<< revPhrasesName << " missing reverse phrase identity exceptions:\n"
<< missing.str();
}
TEST(DictionaryVariantRevPhrasesTest, HKVariantsAreComplete) {
ExpectRevPhrasesComplete("HKVariants");
}
TEST(DictionaryVariantRevPhrasesTest, TWVariantsAreComplete) {
ExpectRevPhrasesComplete("TWVariants");
}
} }