xref: /base/global/i18n/frameworks/intl/src/taboo.cpp (revision 9596a2c1)
1/*
2 * Copyright (c) 2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include <cstring>
17#include <filesystem>
18#include <sys/stat.h>
19#include "i18n_hilog.h"
20#include "libxml/globals.h"
21#include "libxml/tree.h"
22#include "libxml/xmlstring.h"
23#include "locale_compare.h"
24#include "taboo.h"
25
26namespace OHOS {
27namespace Global {
28namespace I18n {
29const char* Taboo::ROOT_TAG = "taboo";
30const char* Taboo::ITEM_TAG = "item";
31const char* Taboo::NAME_TAG = "name";
32const char* Taboo::VALUE_TAG = "value";
33std::set<std::string> Taboo::supportedRegions;
34std::set<std::string> Taboo::supportedLanguages;
35std::map<std::string, std::string> Taboo::RESOURCES = {};
36std::mutex Taboo::TABOO_MUTEX;
37
38Taboo::Taboo()
39{
40}
41
42Taboo::Taboo(const std::string& path) : tabooDataPath(path)
43{
44    using std::filesystem::directory_iterator;
45
46    std::string tabooConfigFilePath = tabooDataPath + tabooConfigFileName;
47    struct stat s;
48    isTabooDataExist = stat(tabooConfigFilePath.c_str(), &s) == 0;
49    if (isTabooDataExist) {
50        if (RESOURCES.size() == 0) {
51            std::lock_guard<std::mutex> tabooLock(TABOO_MUTEX);
52            if (RESOURCES.size() == 0) {
53                // parse taboo-config.xml to obtain supported regions and languages for name replacement.
54                ParseTabooData(tabooConfigFilePath, DataFileType::CONFIG_FILE);
55                ReadResourceList();
56            }
57        }
58    }
59}
60
61Taboo::~Taboo()
62{
63}
64
65std::string Taboo::ReplaceCountryName(const std::string& region, const std::string& displayLanguage,
66    const std::string& name)
67{
68    if (!isTabooDataExist) {
69        HILOG_INFO_I18N("Taboo::ReplaceCountryName Taboo data not exist.");
70        return name;
71    }
72    if (supportedRegions.find(region) == supportedRegions.end()) {
73        HILOG_INFO_I18N("Taboo::ReplaceCountryName taboo data don't support region %{public}s", region.c_str());
74        return name;
75    }
76    std::string key = regionKey + region;
77    std::vector<std::string> fallbackRegionKeys = QueryKeyFallBack(key);
78    std::string fallbackLanguage;
79    std::string fileName;
80    std::tie(fallbackLanguage, fileName) = LanguageFallBack(displayLanguage);
81    if (fallbackLanguage.length() == 0) {
82        HILOG_INFO_I18N("Taboo::ReplaceCountryName language %{public}s fallback failed", displayLanguage.c_str());
83        return name;
84    }
85    if (localeTabooData.find(fallbackLanguage) == localeTabooData.end()) {
86        localeTabooData[fallbackLanguage] = {};
87        std::string localeTabooDataFilePath = tabooDataPath + fileName + filePathSplitor + tabooLocaleDataFileName;
88        ParseTabooData(localeTabooDataFilePath, DataFileType::DATA_FILE, fallbackLanguage);
89    }
90    auto tabooData = localeTabooData[fallbackLanguage];
91    for (auto it = fallbackRegionKeys.begin(); it != fallbackRegionKeys.end(); ++it) {
92        if (tabooData.find(*it) != tabooData.end()) {
93            return tabooData[*it];
94        }
95    }
96    HILOG_INFO_I18N("Taboo::ReplaceCountryName not find taboo data correspond to region %{public}s",
97        region.c_str());
98    return name;
99}
100
101std::string Taboo::ReplaceLanguageName(const std::string& language, const std::string& displayLanguage,
102    const std::string& name)
103{
104    if (!isTabooDataExist) {
105        HILOG_INFO_I18N("Taboo::ReplaceLanguageName Taboo data not exist.");
106        return name;
107    }
108    if (supportedLanguages.find(language) == supportedLanguages.end()) {
109        HILOG_ERROR_I18N("Taboo::ReplaceLanguageName taboo data don't support language %{public}s",
110            language.c_str());
111        return name;
112    }
113    std::string key = languageKey + language;
114    std::vector<std::string> fallbackLanguageKeys = QueryKeyFallBack(key);
115    std::string fallbackLanguage;
116    std::string fileName;
117    std::tie(fallbackLanguage, fileName) = LanguageFallBack(displayLanguage);
118    if (fallbackLanguage.size() == 0) {
119        HILOG_ERROR_I18N("Taboo::ReplaceLanguageName language %{public}s fallback failed", displayLanguage.c_str());
120        return name;
121    }
122    if (localeTabooData.find(fallbackLanguage) == localeTabooData.end()) {
123        localeTabooData[fallbackLanguage] = {};
124        std::string localeTabooDataFilePath = tabooDataPath + fileName + filePathSplitor + tabooLocaleDataFileName;
125        ParseTabooData(localeTabooDataFilePath, DataFileType::DATA_FILE, fallbackLanguage);
126    }
127    auto tabooData = localeTabooData[fallbackLanguage];
128    for (auto it = fallbackLanguageKeys.begin(); it != fallbackLanguageKeys.end(); ++it) {
129        if (tabooData.find(*it) != tabooData.end()) {
130            return tabooData[*it];
131        }
132    }
133    HILOG_ERROR_I18N("Taboo::ReplaceLanguageName not find taboo data correspond to language %{public}s",
134        language.c_str());
135    return name;
136}
137
138void Taboo::ParseTabooData(const std::string& path, DataFileType fileType, const std::string& locale)
139{
140    xmlKeepBlanksDefault(0);
141    xmlDocPtr doc = xmlParseFile(path.c_str());
142    if (doc == nullptr) {
143        HILOG_ERROR_I18N("Taboo parse taboo data file failed: %{public}s", path.c_str());
144        return;
145    }
146    xmlNodePtr cur = xmlDocGetRootElement(doc);
147    if (cur == nullptr || xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(ROOT_TAG)) != 0) {
148        xmlFreeDoc(doc);
149        HILOG_ERROR_I18N("Taboo get root tag from taboo data file failed: %{public}s", path.c_str());
150        return;
151    }
152    cur = cur->xmlChildrenNode;
153    const xmlChar* nameTag = reinterpret_cast<const xmlChar*>(NAME_TAG);
154    const xmlChar* valueTag = reinterpret_cast<const xmlChar*>(VALUE_TAG);
155    while (cur != nullptr && xmlStrcmp(cur->name, reinterpret_cast<const xmlChar*>(ITEM_TAG)) == 0) {
156        xmlChar* name = xmlGetProp(cur, nameTag);
157        xmlChar* value = xmlGetProp(cur, valueTag);
158        if (name == nullptr || value == nullptr) {
159            HILOG_ERROR_I18N("Taboo get name and value property failed: %{public}s", path.c_str());
160            cur = cur->next;
161            continue;
162        }
163        std::string nameStr = reinterpret_cast<const char*>(name);
164        std::string valueStr = reinterpret_cast<const char*>(value);
165        switch (fileType) {
166            case DataFileType::CONFIG_FILE:
167                ProcessTabooConfigData(nameStr, valueStr);
168                break;
169            case DataFileType::DATA_FILE:
170                ProcessTabooLocaleData(locale, nameStr, valueStr);
171                break;
172        }
173        xmlFree(name);
174        xmlFree(value);
175        cur = cur->next;
176    }
177    xmlFreeDoc(doc);
178}
179
180void Taboo::ProcessTabooConfigData(const std::string& name, const std::string& value)
181{
182    if (name.compare(supportedRegionsTag) == 0) {
183        SplitValue(value, supportedRegions);
184    } else if (name.compare(supportedLanguagesTag) == 0) {
185        SplitValue(value, supportedLanguages);
186    }
187}
188
189void Taboo::ProcessTabooLocaleData(const std::string& locale, const std::string& name, const std::string& value)
190{
191    if (localeTabooData.find(locale) != localeTabooData.end()) {
192        localeTabooData[locale][name] = value;
193    } else {
194        std::map<std::string, std::string> data;
195        data[name] = value;
196        localeTabooData[locale] = data;
197    }
198}
199
200void Taboo::SplitValue(const std::string& value, std::set<std::string>& collation)
201{
202    size_t startPos = 0;
203    while (startPos < value.length()) {
204        size_t endPos = value.find(tabooDataSplitor, startPos);
205        if (endPos == std::string::npos) {
206            collation.insert(value.substr(startPos));
207            endPos = value.length();
208        } else {
209            collation.insert(value.substr(startPos, endPos - startPos));
210        }
211        startPos = endPos + 1;
212    }
213}
214
215std::vector<std::string> Taboo::QueryKeyFallBack(const std::string& key)
216{
217    std::vector<std::string> fallback;
218    fallback.push_back(key + "_r_all");
219    return fallback;
220}
221
222std::tuple<std::string, std::string> Taboo::LanguageFallBack(const std::string& language)
223{
224    std::string bestMatch;
225    std::string fileName;
226    int32_t bestScore = -1;
227
228    for (auto it = RESOURCES.begin(); it != RESOURCES.end(); ++it) {
229        std::string resLanguage = it->first;
230        int32_t score = LocaleCompare::Compare(language, resLanguage);
231        if (score > bestScore) {
232            bestMatch = resLanguage;
233            fileName = it->second;
234            bestScore = score;
235        }
236    }
237    if (bestScore < 0) {
238        return std::make_tuple("", "");
239    }
240    return std::make_tuple(bestMatch, fileName);
241}
242
243void Taboo::ReadResourceList()
244{
245    using std::filesystem::directory_iterator;
246    struct stat s;
247    for (const auto &dirEntry : directory_iterator{tabooDataPath}) {
248        std::string path = dirEntry.path();
249        if (stat(path.c_str(), &s) != 0) {
250            HILOG_ERROR_I18N("get path status failed");
251            continue;
252        }
253        if (s.st_mode & S_IFDIR) {
254            std::string fileName = path.substr(tabooDataPath.length());
255            std::string language = GetLanguageFromFileName(fileName);
256            RESOURCES[language] = fileName;
257        }
258    }
259}
260
261std::string Taboo::GetLanguageFromFileName(const std::string& fileName)
262{
263    if (fileName.length() == 3) { // 3 is the length of file 'xml'
264        return "en";
265    }
266    std::string language = fileName.substr(4);
267    if (language[0] == 'b' && language[1] == '+') {
268        language = language.substr(2); // 2 is the length of "b+"
269    }
270    size_t pos = language.find("+");
271    if (pos != std::string::npos) {
272        language = language.replace(pos, 1, "-");
273    }
274    pos = language.find("-r");
275    if (pos != std::string::npos) {
276        language = language.replace(pos, 2, "-"); // 2 is the length of "-r"
277    }
278    return language;
279}
280} // namespace I18n
281} // namespace Global
282} // namespace OHOS