1 /*
2  * Copyright (c) 2021 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 "utils/string_utils.h"
17 
18 #include <cctype>
19 #include <cstdarg>
20 #include <cstdint>
21 #include <limits>
22 #include <vector>
23 #include <regex>
24 #include <algorithm>
25 #include "hilog_wrapper.h"
26 
27 #ifdef SUPPORT_GRAPHICS
28 #include "unicode/numberformatter.h"
29 #endif
30 
31 #if defined(__WINNT__)
32 #include <cstring>
33 #else
34 #include "securec.h"
35 #endif
36 
37 namespace OHOS {
38 namespace Global {
39 namespace Resource {
40 const std::regex PLACEHOLDER_MATCHING_RULES(R"((%%)|%((\d+)\$){0,1}(\.\d)?([dsf]))");
41 // the whole string match the regex, such as "%1$.2f" in the string "count is %1$.2f"
42 constexpr uint8_t MATCHE_INDEX_WHOLE_STRING = 0;
43 // match %%
44 constexpr uint8_t MATCHE_INDEX_DOUBLE_PERCENT = 1;
45 // match the placeholder index number, such as 1 in 1$
46 constexpr uint8_t MATCHE_INDEX_PLACEHOLDER_INDEX = 3;
47 // match precision, such as .2f
48 constexpr uint8_t MATCHE_INDEX_PRECISION = 4;
49 // match type [dsf]
50 constexpr uint8_t MATCHE_INDEX_PLACEHOLDER_TYPE = 5;
51 constexpr int32_t INVALID_PRECISION = -1;
52 const std::string SIZE_T_MAX_STR = std::to_string(std::numeric_limits<size_t>::max());
53 #ifdef SUPPORT_GRAPHICS
54 #endif
55 
FormatString(const char *fmt, ...)56 std::string FormatString(const char *fmt, ...)
57 {
58     std::string strResult;
59     if (fmt != nullptr) {
60         va_list marker;
61         va_start(marker, fmt);
62         strResult = FormatString(fmt, marker);
63         va_end(marker);
64     }
65     return strResult;
66 }
67 
FormatString(const char *fmt, va_list args)68 std::string FormatString(const char *fmt, va_list args)
69 {
70     std::string strResult;
71     if (fmt != nullptr) {
72         va_list tmpArgs;
73         va_copy(tmpArgs, args);
74         int nLength = vsnprintf(nullptr, 0, fmt, tmpArgs); // compute buffer size
75         va_end(tmpArgs);
76         std::vector<char> vBuffer(nLength + 1, '\0');
77         int nWritten = vsnprintf_s(&vBuffer[0], nLength + 1, nLength, fmt, args);
78         if (nWritten > 0) {
79             strResult = &vBuffer[0];
80         }
81     }
82     return strResult;
83 }
84 
getJsParams(const std::string &inputOutputValue, va_list args, std::vector<std::pair<int, std::string>> paramsWithOutNum, std::vector<std::pair<int, std::string>> paramsWithNum, std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)85 bool getJsParams(const std::string &inputOutputValue, va_list args,
86     std::vector<std::pair<int, std::string>> paramsWithOutNum,
87     std::vector<std::pair<int, std::string>> paramsWithNum,
88     std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)
89 {
90     std::sort(paramsWithNum.begin(), paramsWithNum.end(),
91         [](const std::pair<int, std::string>& a, const std::pair<int, std::string>& b) {
92             return a.first < b.first;
93         });
94     for (size_t i = 0; i < paramsWithOutNum.size(); i++) {
95         std::string type = paramsWithOutNum[i].second;
96         if (type == "d") {
97             int temp = va_arg(args, int);
98             jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_NUMBER, std::to_string(temp));
99         } else if (type == "s") {
100             char *temp = va_arg(args, char*);
101             jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_STRING, temp);
102         } else if (type == "f") {
103             float temp = va_arg(args, double);
104             jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_NUMBER, std::to_string(temp));
105         }
106     }
107     for (size_t i = 0; i < paramsWithNum.size(); i++) {
108         size_t index = static_cast<size_t>(paramsWithNum[i].first);
109         std::string type = paramsWithNum[i].second;
110         if (index < paramsWithOutNum.size()) {
111             if (type != paramsWithOutNum[index].second) {
112                 return false;
113             }
114         } else if (index == paramsWithOutNum.size()) {
115             paramsWithOutNum.push_back({index, type});
116             if (type == "d") {
117                 int temp = va_arg(args, int);
118                 jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_NUMBER, std::to_string(temp));
119             } else if (type == "s") {
120                 char *temp = va_arg(args, char*);
121                 jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_STRING, temp);
122             } else if (type == "f") {
123                 float temp = va_arg(args, double);
124                 jsParams.emplace_back(ResourceManager::NapiValueType::NAPI_NUMBER, std::to_string(temp));
125             }
126         } else {
127             return false;
128         }
129     }
130     return true;
131 }
132 
parseArgs(const std::string &inputOutputValue, va_list args, std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)133 bool parseArgs(const std::string &inputOutputValue, va_list args,
134     std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)
135 {
136     if (inputOutputValue.empty()) {
137         return true;
138     }
139     std::string::const_iterator start = inputOutputValue.begin();
140     std::string::const_iterator end = inputOutputValue.end();
141     std::smatch matches;
142     size_t matchCount = 0;
143     int prefixLength = 0;
144     int offset = 2;
145     std::vector<std::pair<int, std::string>> paramsWithOutNum;
146     std::vector<std::pair<int, std::string>> paramsWithNum;
147     while (std::regex_search(start, end, matches, PLACEHOLDER_MATCHING_RULES)) {
148         prefixLength = matches[MATCHE_INDEX_WHOLE_STRING].first - inputOutputValue.begin();
149         if (matches[1].length() != 0) {
150             start = inputOutputValue.begin() + prefixLength + offset;
151             continue;
152         }
153         std::string placeholderIndex = matches[MATCHE_INDEX_PLACEHOLDER_INDEX];
154         std::string placeholderType = matches[MATCHE_INDEX_PLACEHOLDER_TYPE];
155         size_t paramIndex;
156         if (placeholderIndex.length() != 0) {
157             if (placeholderIndex.size() > SIZE_T_MAX_STR.size() ||
158                 (placeholderIndex.size() == SIZE_T_MAX_STR.size() && placeholderIndex > SIZE_T_MAX_STR)) {
159                 RESMGR_HILOGE(RESMGR_TAG, "index of placeholder is too large");
160                 return false;
161             }
162             if (std::stoul(placeholderIndex) == 0) {
163                 return false;
164             }
165             paramIndex = std::stoul(placeholderIndex) - 1;
166             paramsWithNum.push_back({paramIndex, placeholderType});
167         } else {
168             paramIndex = matchCount++;
169             paramsWithOutNum.push_back({paramIndex, placeholderType});
170         }
171         start = inputOutputValue.begin() + prefixLength + matches[0].length();
172     }
173     return getJsParams(inputOutputValue, args, paramsWithOutNum, paramsWithNum, jsParams);
174 }
175 
LocalizeNumber(std::string &inputOutputNum, const ResConfigImpl &resConfig, const int32_t precision = INVALID_PRECISION)176 bool LocalizeNumber(std::string &inputOutputNum, const ResConfigImpl &resConfig,
177     const int32_t precision = INVALID_PRECISION)
178 {
179 #ifdef SUPPORT_GRAPHICS
180     const ResLocale *resLocale = resConfig.GetResLocale();
181     if (resLocale == nullptr) {
182         RESMGR_HILOGW(RESMGR_TAG, "LocalizeNumber resLocale is null");
183         return true;
184     }
185 
186     std::string localeInfo;
187     const char *language = resLocale->GetLanguage();
188     if (language != nullptr && strlen(language) > 0) {
189         localeInfo.assign(language);
190     } else {
191         RESMGR_HILOGW(RESMGR_TAG, "LocalizeNumber language is null");
192         return true;
193     }
194     std::string temp;
195     const char *script = resLocale->GetScript();
196     if (script != nullptr && strlen(script) > 0) {
197         temp.assign(script);
198         localeInfo += "-" + temp;
199     }
200     const char *region = resLocale->GetRegion();
201     if (region != nullptr && strlen(region) > 0) {
202         temp.assign(region);
203         localeInfo += "-" + temp;
204     }
205 
206     icu::Locale locale(localeInfo.c_str());
207     if (locale.isBogus()) {
208         return true;
209     }
210 
211     icu::number::LocalizedNumberFormatter numberFormat = icu::number::NumberFormatter::withLocale(locale);
212     numberFormat = numberFormat.grouping(UNumberGroupingStrategy::UNUM_GROUPING_OFF);
213     numberFormat = numberFormat.roundingMode(UNUM_ROUND_HALFUP);
214     if (precision != INVALID_PRECISION) {
215         numberFormat = numberFormat.precision(icu::number::Precision::fixedFraction(precision));
216     }
217     UErrorCode status = U_ZERO_ERROR;
218     double num = std::stod(inputOutputNum);
219     inputOutputNum.clear();
220     numberFormat.formatDouble(num, status).toString(status).toUTF8String(inputOutputNum);
221     if (status == U_ZERO_ERROR) {
222         RESMGR_HILOGE(RESMGR_TAG, "LocalizeNumber failed, status = %{public}d", status);
223         return false;
224     }
225     return true;
226 #else
227     return true;
228 #endif
229 }
230 
GetReplaceStr(const std::tuple<ResourceManager::NapiValueType, std::string> &jsParam, const std::string &placeHolderType, int32_t precision, const ResConfigImpl &config, std::string &replaceStr)231 bool GetReplaceStr(const std::tuple<ResourceManager::NapiValueType, std::string> &jsParam,
232     const std::string &placeHolderType, int32_t precision, const ResConfigImpl &config, std::string &replaceStr)
233 {
234     ResourceManager::NapiValueType paramType = std::get<0>(jsParam);
235     std::string paramValue = std::get<1>(jsParam);
236 
237     // string type
238     if (placeHolderType == "s") {
239         if (paramType != ResourceManager::NapiValueType::NAPI_STRING) {
240             RESMGR_HILOGE(RESMGR_TAG, "the type of placeholder and param does not match");
241             return false;
242         }
243         replaceStr = paramValue;
244         return true;
245     }
246 
247     // number type
248     if (paramType != ResourceManager::NapiValueType::NAPI_NUMBER) {
249         RESMGR_HILOGE(RESMGR_TAG, "the type of placeholder and param does not match");
250         return false;
251     }
252 
253     // int type
254     if (placeHolderType == "d") {
255         size_t posOfDecimalPoint = paramValue.find(".");
256         replaceStr = paramValue.substr(0, posOfDecimalPoint);
257         return LocalizeNumber(replaceStr, config);
258     }
259 
260     // double type
261     replaceStr = paramValue;
262     return LocalizeNumber(replaceStr, config, precision);
263 }
264 
MatchPlaceholderIndex(std::string placeholderIndex, size_t &paramIndex, size_t &matchCount)265 bool MatchPlaceholderIndex(std::string placeholderIndex, size_t &paramIndex, size_t &matchCount)
266 {
267     if (placeholderIndex.length() != 0) {
268         if (placeholderIndex.size() > SIZE_T_MAX_STR.size() ||
269             (placeholderIndex.size() == SIZE_T_MAX_STR.size() && placeholderIndex > SIZE_T_MAX_STR)) {
270             RESMGR_HILOGE(RESMGR_TAG, "index of placeholder is too large");
271             return false;
272         }
273         if (std::stoul(placeholderIndex) < 1) {
274             return false;
275         }
276         paramIndex = std::stoul(placeholderIndex) - 1;
277     } else {
278         paramIndex = matchCount++;
279     }
280     return true;
281 }
282 
GetPrecision(const std::string &precisionStr)283 int32_t GetPrecision(const std::string &precisionStr)
284 {
285     size_t posOfDecimalPoint = precisionStr.find(".");
286     if (posOfDecimalPoint != std::string::npos) {
287         return std::stoi(precisionStr.substr(posOfDecimalPoint + 1));
288     }
289     return INVALID_PRECISION;
290 }
291 
ReplacePlaceholderWithParams(std::string &inputOutputValue, const ResConfigImpl &resConfig, const std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)292 bool ReplacePlaceholderWithParams(std::string &inputOutputValue, const ResConfigImpl &resConfig,
293     const std::vector<std::tuple<ResourceManager::NapiValueType, std::string>> &jsParams)
294 {
295     if (inputOutputValue.empty()) {
296         return true;
297     }
298 
299     std::string::const_iterator start = inputOutputValue.begin();
300     std::string::const_iterator end = inputOutputValue.end();
301     std::smatch matches;
302     size_t matchCount = 0;
303     int prefixLength = 0;
304 
305     while (std::regex_search(start, end, matches, PLACEHOLDER_MATCHING_RULES)) {
306         prefixLength = matches[MATCHE_INDEX_WHOLE_STRING].first - inputOutputValue.begin();
307         // Matched to %%, replace it with %
308         if (matches[MATCHE_INDEX_DOUBLE_PERCENT].length() != 0) {
309             inputOutputValue.erase(matches[MATCHE_INDEX_DOUBLE_PERCENT].first);
310             start = inputOutputValue.begin() + prefixLength + 1;
311             end = inputOutputValue.end();
312             continue;
313         } else if (jsParams.size() == 0) { // Matched to placeholder but no params, ignore placehold
314             start = inputOutputValue.begin() + prefixLength + matches[0].length();
315             continue;
316         }
317 
318         // Matched to placeholder, check and parse param index
319         std::string placeholderIndex = matches[MATCHE_INDEX_PLACEHOLDER_INDEX];
320         size_t paramIndex;
321         if (!MatchPlaceholderIndex(placeholderIndex, paramIndex, matchCount)) {
322             return false;
323         }
324         if (paramIndex >= jsParams.size()) {
325             RESMGR_HILOGE(RESMGR_TAG, "index of placeholder out of range");
326             return false;
327         }
328         // Replace placeholder with corresponding param
329         std::string replaceStr;
330         int32_t precision = GetPrecision(matches[MATCHE_INDEX_PRECISION]);
331         std::string placeholderType = matches[MATCHE_INDEX_PLACEHOLDER_TYPE];
332         if (!GetReplaceStr(jsParams[paramIndex], placeholderType, precision, resConfig, replaceStr)) {
333             return false;
334         }
335         inputOutputValue.replace(prefixLength, matches[MATCHE_INDEX_WHOLE_STRING].length(), replaceStr);
336 
337         // Update iterator
338         start = inputOutputValue.begin() + prefixLength + replaceStr.length();
339         end = inputOutputValue.end();
340     }
341     return true;
342 }
343 } // namespace Resource
344 } // namespace Global
345 } // namespace OHOS