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 "hilog/log.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
26 namespace OHOS {
27 namespace Global {
28 namespace I18n {
29 static constexpr OHOS::HiviewDFX::HiLogLabel LABEL = { LOG_CORE, 0xD001E00, "Taboo" };
30 using namespace OHOS::HiviewDFX;
31 const char* Taboo::ROOT_TAG = "taboo";
32 const char* Taboo::ITEM_TAG = "item";
33 const char* Taboo::NAME_TAG = "name";
34 const char* Taboo::VALUE_TAG = "value";
35 std::set<std::string> Taboo::supportedRegions;
36 std::set<std::string> Taboo::supportedLanguages;
37 std::map<std::string, std::string> Taboo::RESOURCES = {};
38 std::mutex Taboo::TABOO_MUTEX;
39
Taboo()40 Taboo::Taboo()
41 {
42 }
43
Taboo(const std::string & path)44 Taboo::Taboo(const std::string& path) : tabooDataPath(path)
45 {
46 using std::filesystem::directory_iterator;
47
48 std::string tabooConfigFilePath = tabooDataPath + tabooConfigFileName;
49 struct stat s;
50 isTabooDataExist = stat(tabooConfigFilePath.c_str(), &s) == 0;
51 if (isTabooDataExist) {
52 std::lock_guard<std::mutex> tabooLock(TABOO_MUTEX);
53 if (RESOURCES.size() == 0) {
54 // parse taboo-config.xml to obtain supported regions and languages for name replacement.
55 ParseTabooData(tabooConfigFilePath, DataFileType::CONFIG_FILE);
56 ReadResourceList();
57 }
58 }
59 }
60
~Taboo()61 Taboo::~Taboo()
62 {
63 }
64
ReplaceCountryName(const std::string & region,const std::string & displayLanguage,const std::string & name)65 std::string Taboo::ReplaceCountryName(const std::string& region, const std::string& displayLanguage,
66 const std::string& name)
67 {
68 if (!isTabooDataExist) {
69 HiLog::Info(LABEL, "Taboo::ReplaceCountryName Taboo data not exist.");
70 return name;
71 }
72 if (supportedRegions.find(region) == supportedRegions.end()) {
73 HiLog::Info(LABEL, "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(LABEL, "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(LABEL, "Taboo::ReplaceCountryName not find taboo data correspond to region %{public}s",
97 region.c_str());
98 return name;
99 }
100
ReplaceLanguageName(const std::string & language,const std::string & displayLanguage,const std::string & name)101 std::string Taboo::ReplaceLanguageName(const std::string& language, const std::string& displayLanguage,
102 const std::string& name)
103 {
104 if (!isTabooDataExist) {
105 HiLog::Info(LABEL, "Taboo::ReplaceLanguageName Taboo data not exist.");
106 return name;
107 }
108 if (supportedLanguages.find(language) == supportedLanguages.end()) {
109 HiLog::Error(LABEL, "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(LABEL, "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(LABEL, "Taboo::ReplaceLanguageName not find taboo data correspond to language %{public}s",
134 language.c_str());
135 return name;
136 }
137
ParseTabooData(const std::string & path,DataFileType fileType,const std::string & locale)138 void 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(LABEL, "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(LABEL, "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(LABEL, "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
ProcessTabooConfigData(const std::string & name,const std::string & value)180 void 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
ProcessTabooLocaleData(const std::string & locale,const std::string & name,const std::string & value)189 void 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
SplitValue(const std::string & value,std::set<std::string> & collation)200 void 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
QueryKeyFallBack(const std::string & key)215 std::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
LanguageFallBack(const std::string & language)222 std::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
ReadResourceList()243 void 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(LABEL, "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
GetLanguageFromFileName(const std::string & fileName)261 std::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