• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021-2024 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 "json_compiler.h"
17 #include <iostream>
18 #include <limits>
19 #include <regex>
20 #include "restool_errors.h"
21 #include "translatable_parser.h"
22 
23 namespace OHOS {
24 namespace Global {
25 namespace Restool {
26 using namespace std;
27 const string TAG_NAME = "name";
28 const string TAG_VALUE = "value";
29 const string TAG_PARENT = "parent";
30 const string TAG_QUANTITY = "quantity";
31 const vector<string> QUANTITY_ATTRS = { "zero", "one", "two", "few", "many", "other" };
32 const vector<string> TRANSLATION_TYPE = { "string", "strarray", "plural" };
33 
JsonCompiler(ResType type,const string & output)34 JsonCompiler::JsonCompiler(ResType type, const string &output)
35     : IResourceCompiler(type, output), isBaseString_(false), root_(nullptr)
36 {
37     InitParser();
38 }
39 
~JsonCompiler()40 JsonCompiler::~JsonCompiler()
41 {
42     if (root_) {
43         cJSON_Delete(root_);
44     }
45 }
46 
CompileSingleFile(const FileInfo & fileInfo)47 uint32_t JsonCompiler::CompileSingleFile(const FileInfo &fileInfo)
48 {
49     if (fileInfo.limitKey == "base" &&
50         fileInfo.fileCluster == "element" &&
51         fileInfo.filename == ID_DEFINED_FILE) {
52         return RESTOOL_SUCCESS;
53     }
54 
55     if (!ResourceUtil::OpenJsonFile(fileInfo.filePath, &root_)) {
56         return RESTOOL_ERROR;
57     }
58     if (!root_ || !cJSON_IsObject(root_)) {
59         cerr << "Error: JSON file parsing failed, please check the JSON file.";
60         cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
61         return RESTOOL_ERROR;
62     }
63     cJSON *item = root_->child;
64     if (cJSON_GetArraySize(root_) != 1) {
65         cerr << "Error: node of a JSON file can only have one member, please check the JSON file.";
66         cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
67         return RESTOOL_ERROR;
68     }
69 
70     string tag = item->string;
71     auto ret = g_contentClusterMap.find(tag);
72     if (ret == g_contentClusterMap.end()) {
73         cerr << "Error: invalid tag name '" << tag << "', please check the JSON file.";
74         cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
75         return RESTOOL_ERROR;
76     }
77     isBaseString_ = (fileInfo.limitKey == "base" &&
78         find(TRANSLATION_TYPE.begin(), TRANSLATION_TYPE.end(), tag) != TRANSLATION_TYPE.end());
79     FileInfo copy = fileInfo;
80     copy.fileType = ret->second;
81     if (!ParseJsonArrayLevel(item, copy)) {
82         return RESTOOL_ERROR;
83     }
84     return RESTOOL_SUCCESS;
85 }
86 
87 // below private
InitParser()88 void JsonCompiler::InitParser()
89 {
90     using namespace placeholders;
91     handles_.emplace(ResType::STRING, bind(&JsonCompiler::HandleString, this, _1, _2));
92     handles_.emplace(ResType::INTEGER, bind(&JsonCompiler::HandleInteger, this, _1, _2));
93     handles_.emplace(ResType::BOOLEAN, bind(&JsonCompiler::HandleBoolean, this, _1, _2));
94     handles_.emplace(ResType::COLOR, bind(&JsonCompiler::HandleColor, this, _1, _2));
95     handles_.emplace(ResType::FLOAT, bind(&JsonCompiler::HandleFloat, this, _1, _2));
96     handles_.emplace(ResType::STRARRAY, bind(&JsonCompiler::HandleStringArray, this, _1, _2));
97     handles_.emplace(ResType::INTARRAY, bind(&JsonCompiler::HandleIntegerArray, this, _1, _2));
98     handles_.emplace(ResType::THEME, bind(&JsonCompiler::HandleTheme, this, _1, _2));
99     handles_.emplace(ResType::PATTERN, bind(&JsonCompiler::HandlePattern, this, _1, _2));
100     handles_.emplace(ResType::PLURAL, bind(&JsonCompiler::HandlePlural, this, _1, _2));
101     handles_.emplace(ResType::SYMBOL, bind(&JsonCompiler::HandleSymbol, this, _1, _2));
102 }
103 
ParseJsonArrayLevel(const cJSON * arrayNode,const FileInfo & fileInfo)104 bool JsonCompiler::ParseJsonArrayLevel(const cJSON *arrayNode, const FileInfo &fileInfo)
105 {
106     if (!arrayNode || !cJSON_IsArray(arrayNode)) {
107         cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' must be array.";
108         cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
109         return false;
110     }
111 
112     if (cJSON_GetArraySize(arrayNode) == 0) {
113         cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' empty.";
114         cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
115         return false;
116     }
117     int32_t index = -1;
118     for (cJSON *item = arrayNode->child; item; item = item->next) {
119         index++;
120         if (!item || !cJSON_IsObject(item)) {
121             cerr << "Error: the seq=" << index << " item must be object." << NEW_LINE_PATH << fileInfo.filePath << endl;
122             return false;
123         }
124         if (!ParseJsonObjectLevel(item, fileInfo)) {
125             return false;
126         }
127     }
128     return true;
129 }
130 
ParseJsonObjectLevel(cJSON * objectNode,const FileInfo & fileInfo)131 bool JsonCompiler::ParseJsonObjectLevel(cJSON *objectNode, const FileInfo &fileInfo)
132 {
133     cJSON *nameNode = cJSON_GetObjectItem(objectNode, TAG_NAME.c_str());
134     if (!nameNode) {
135         cerr << "Error: name empty." << NEW_LINE_PATH << fileInfo.filePath << endl;
136         return false;
137     }
138 
139     if (!cJSON_IsString(nameNode)) {
140         cerr << "Error: name must string." << NEW_LINE_PATH << fileInfo.filePath << endl;
141         return false;
142     }
143 
144     if (isBaseString_ && !TranslatableParse::ParseTranslatable(objectNode, fileInfo, nameNode->valuestring)) {
145         return false;
146     }
147     ResourceItem resourceItem(nameNode->valuestring, fileInfo.keyParams, fileInfo.fileType);
148     resourceItem.SetFilePath(fileInfo.filePath);
149     resourceItem.SetLimitKey(fileInfo.limitKey);
150     auto ret = handles_.find(fileInfo.fileType);
151     if (ret == handles_.end()) {
152         cerr << "Error: json parser don't support " << ResourceUtil::ResTypeToString(fileInfo.fileType) << endl;
153         return false;
154     }
155 
156     if (!ret->second(objectNode, resourceItem)) {
157         return false;
158     }
159 
160     return MergeResourceItem(resourceItem);
161 }
162 
HandleString(const cJSON * objectNode,ResourceItem & resourceItem) const163 bool JsonCompiler::HandleString(const cJSON *objectNode, ResourceItem &resourceItem) const
164 {
165     cJSON *valueNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
166     if (!CheckJsonStringValue(valueNode, resourceItem)) {
167         return false;
168     }
169     return PushString(valueNode->valuestring, resourceItem);
170 }
171 
HandleInteger(const cJSON * objectNode,ResourceItem & resourceItem) const172 bool JsonCompiler::HandleInteger(const cJSON *objectNode, ResourceItem &resourceItem) const
173 {
174     cJSON *valueNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
175     if (!CheckJsonIntegerValue(valueNode, resourceItem)) {
176         return false;
177     }
178     if (cJSON_IsString(valueNode)) {
179         return PushString(valueNode->valuestring, resourceItem);
180     } else if (cJSON_IsNumber(valueNode)) {
181         return PushString(to_string(valueNode->valueint), resourceItem);
182     } else {
183         return false;
184     }
185 }
186 
HandleBoolean(const cJSON * objectNode,ResourceItem & resourceItem) const187 bool JsonCompiler::HandleBoolean(const cJSON *objectNode, ResourceItem &resourceItem) const
188 {
189     cJSON *valueNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
190     if (cJSON_IsString(valueNode)) {
191         regex ref("^\\$(ohos:)?boolean:.*");
192         if (!regex_match(valueNode->valuestring, ref)) {
193             cerr << "Error: '" << valueNode->valuestring << "' only refer '$boolean:xxx'.";
194             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
195             return false;
196         }
197         return PushString(valueNode->valuestring, resourceItem);
198     }
199     if (!cJSON_IsBool(valueNode)) {
200         cerr << "Error: '" << resourceItem.GetName() << "' value not boolean.";
201         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
202         return false;
203     }
204     return PushString(cJSON_IsTrue(valueNode) == 1 ? "true" : "false", resourceItem);
205 }
206 
HandleColor(const cJSON * objectNode,ResourceItem & resourceItem) const207 bool JsonCompiler::HandleColor(const cJSON *objectNode, ResourceItem &resourceItem) const
208 {
209     return HandleString(objectNode, resourceItem);
210 }
211 
HandleFloat(const cJSON * objectNode,ResourceItem & resourceItem) const212 bool JsonCompiler::HandleFloat(const cJSON *objectNode, ResourceItem &resourceItem) const
213 {
214     return HandleString(objectNode, resourceItem);
215 }
216 
HandleStringArray(const cJSON * objectNode,ResourceItem & resourceItem) const217 bool JsonCompiler::HandleStringArray(const cJSON *objectNode, ResourceItem &resourceItem) const
218 {
219     vector<string> extra;
220     return ParseValueArray(objectNode, resourceItem, extra,
221         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
222             if (!cJSON_IsObject(arrayItem)) {
223                 cerr << "Error: '" << resourceItem.GetName() << "' value array item not object.";
224                 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
225                 return false;
226             }
227             cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
228             if (!CheckJsonStringValue(valueNode, resourceItem)) {
229                 return false;
230             }
231             values.push_back(valueNode->valuestring);
232             return true;
233     });
234 }
235 
HandleIntegerArray(const cJSON * objectNode,ResourceItem & resourceItem) const236 bool JsonCompiler::HandleIntegerArray(const cJSON *objectNode, ResourceItem &resourceItem) const
237 {
238     vector<string> extra;
239     return ParseValueArray(objectNode, resourceItem, extra,
240         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
241             if (!CheckJsonIntegerValue(arrayItem, resourceItem)) {
242                 return false;
243             }
244             if (cJSON_IsString(arrayItem)) {
245                 values.push_back(arrayItem->valuestring);
246             } else {
247                 values.push_back(to_string(arrayItem->valueint));
248             }
249             return true;
250     });
251 }
252 
HandleTheme(const cJSON * objectNode,ResourceItem & resourceItem) const253 bool JsonCompiler::HandleTheme(const cJSON *objectNode, ResourceItem &resourceItem) const
254 {
255     vector<string> extra;
256     if (!ParseParent(objectNode, resourceItem, extra)) {
257         return false;
258     }
259     return ParseValueArray(objectNode, resourceItem, extra,
260         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
261             return ParseAttribute(arrayItem, resourceItem, values);
262         });
263 }
264 
HandlePattern(const cJSON * objectNode,ResourceItem & resourceItem) const265 bool JsonCompiler::HandlePattern(const cJSON *objectNode, ResourceItem &resourceItem) const
266 {
267     return HandleTheme(objectNode, resourceItem);
268 }
269 
HandlePlural(const cJSON * objectNode,ResourceItem & resourceItem) const270 bool JsonCompiler::HandlePlural(const cJSON *objectNode, ResourceItem &resourceItem) const
271 {
272     vector<string> extra;
273     vector<string> attrs;
274     bool result = ParseValueArray(objectNode, resourceItem, extra,
275         [&attrs, this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
276             if (!CheckPluralValue(arrayItem, resourceItem)) {
277                 return false;
278             }
279             cJSON *quantityNode = cJSON_GetObjectItem(arrayItem, TAG_QUANTITY.c_str());
280             if (!quantityNode || !cJSON_IsString(quantityNode)) {
281                 return false;
282             }
283             string quantityValue = quantityNode->valuestring;
284             if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) {
285                 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
286                 cerr << "' duplicated." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
287                 return false;
288             }
289             attrs.push_back(quantityValue);
290             values.push_back(quantityValue);
291             cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
292             if (!valueNode || !cJSON_IsString(valueNode)) {
293                 return false;
294             }
295             values.push_back(valueNode->valuestring);
296             return true;
297         });
298     if (!result) {
299         return false;
300     }
301     if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) {
302         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other'.";
303         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
304         return false;
305     }
306     return true;
307 }
308 
HandleSymbol(const cJSON * objectNode,ResourceItem & resourceItem) const309 bool JsonCompiler::HandleSymbol(const cJSON *objectNode, ResourceItem &resourceItem) const
310 {
311     cJSON *valueNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
312     if (!CheckJsonSymbolValue(valueNode, resourceItem)) {
313         return false;
314     }
315     return PushString(valueNode->valuestring, resourceItem);
316 }
317 
PushString(const string & value,ResourceItem & resourceItem) const318 bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const
319 {
320     if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) {
321         cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "'.";
322         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
323         return false;
324     }
325     return true;
326 }
327 
CheckJsonStringValue(const cJSON * valueNode,const ResourceItem & resourceItem) const328 bool JsonCompiler::CheckJsonStringValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
329 {
330     if (!valueNode || !cJSON_IsString(valueNode)) {
331         cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
332         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
333         return false;
334     }
335 
336     const map<ResType, string> REFS = {
337         { ResType::STRING, "\\$(ohos:)?string:" },
338         { ResType::STRARRAY, "\\$(ohos:)?string:" },
339         { ResType::COLOR, "\\$(ohos:)?color:" },
340         { ResType::FLOAT, "\\$(ohos:)?float:" }
341     };
342 
343     string value = valueNode->valuestring;
344     ResType type = resourceItem.GetResType();
345     if (type ==  ResType::COLOR && !CheckColorValue(value.c_str())) {
346         string error = "invalid color value '" + value + \
347                         "', only support refer '$color:xxx' or '#rgb','#argb','#rrggbb','#aarrggbb'.";
348         cerr << "Error: " << error << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
349         return false;
350     }
351     regex ref("^\\$.+:");
352     smatch result;
353     if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) {
354         cerr << "Error: '" << value << "', only refer '"<< REFS.at(type) << "xxx'.";
355         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
356         return false;
357     }
358     return true;
359 }
360 
CheckJsonIntegerValue(const cJSON * valueNode,const ResourceItem & resourceItem) const361 bool JsonCompiler::CheckJsonIntegerValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
362 {
363     if (!valueNode) {
364         cerr << "Error: '" << resourceItem.GetName() << "' value is empty";
365         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
366         return false;
367     }
368     if (cJSON_IsString(valueNode)) {
369         regex ref("^\\$(ohos:)?integer:.*");
370         if (!regex_match(valueNode->valuestring, ref)) {
371             cerr << "Error: '" << valueNode->valuestring << "', only refer '$integer:xxx'.";
372             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
373             return false;
374         }
375     } else if (!ResourceUtil::IsIntValue(valueNode)) {
376         cerr << "Error: '" << resourceItem.GetName() << "' value not integer.";
377         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
378         return false;
379     }
380     return true;
381 }
382 
CheckJsonSymbolValue(const cJSON * valueNode,const ResourceItem & resourceItem) const383 bool JsonCompiler::CheckJsonSymbolValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
384 {
385     if (!valueNode || !cJSON_IsString(valueNode)) {
386         cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
387         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
388         return false;
389     }
390     string unicodeStr = valueNode->valuestring;
391     if (regex_match(unicodeStr, regex("^\\$(ohos:)?symbol:.*"))) {
392         return true;
393     }
394     int unicode = strtol(unicodeStr.c_str(), nullptr, 16);
395     if (!ResourceUtil::isUnicodeInPlane15or16(unicode)) {
396         cerr << "Error: '" << resourceItem.GetName() << "' value must in 0xF0000 ~ 0xFFFFF or 0x100000 ~ 0x10FFFF.";
397         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
398         return false;
399     }
400     return true;
401 }
402 
ParseValueArray(const cJSON * objectNode,ResourceItem & resourceItem,const vector<string> & extra,HandleValue callback) const403 bool JsonCompiler::ParseValueArray(const cJSON *objectNode, ResourceItem &resourceItem,
404                                    const vector<string> &extra, HandleValue callback) const
405 {
406     cJSON *arrayNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
407     if (!cJSON_IsArray(arrayNode)) {
408         cerr << "Error: '" << resourceItem.GetName() << "' value not array.";
409         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
410         return false;
411     }
412 
413     if (cJSON_GetArraySize(arrayNode) == 0) {
414         cerr << "Error: '" << resourceItem.GetName() << "' value empty.";
415         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
416         return false;
417     }
418 
419     vector<string> contents;
420     if (!extra.empty()) {
421         contents.assign(extra.begin(), extra.end());
422     }
423     for (cJSON *item = arrayNode->child; item; item = item->next) {
424         vector<string> values;
425         if (!callback(item, resourceItem, values)) {
426             return false;
427         }
428         contents.insert(contents.end(), values.begin(), values.end());
429     }
430 
431     string data = ResourceUtil::ComposeStrings(contents);
432     if (data.empty()) {
433         cerr << "Error: '" << resourceItem.GetName() << "' array too large.";
434         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
435         return false;
436     }
437     return PushString(data, resourceItem);
438 }
439 
ParseParent(const cJSON * objectNode,const ResourceItem & resourceItem,vector<string> & extra) const440 bool JsonCompiler::ParseParent(const cJSON *objectNode, const ResourceItem &resourceItem,
441                                vector<string> &extra) const
442 {
443     cJSON *parentNode = cJSON_GetObjectItem(objectNode, TAG_PARENT.c_str());
444     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
445     if (parentNode) {
446         if (!cJSON_IsString(parentNode)) {
447             cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string.";
448             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
449             return false;
450         }
451         string parentValue = parentNode->valuestring;
452         if (parentValue.empty()) {
453             cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty.";
454             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
455             return false;
456         }
457         if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) {
458             parentValue = "$" + parentValue;
459         } else {
460             parentValue = "$" + type + ":" + parentValue;
461         }
462         extra.push_back(parentValue);
463     }
464     return true;
465 }
466 
ParseAttribute(const cJSON * arrayItem,const ResourceItem & resourceItem,vector<string> & values) const467 bool JsonCompiler::ParseAttribute(const cJSON *arrayItem, const ResourceItem &resourceItem,
468                                   vector<string> &values) const
469 {
470     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
471     if (!arrayItem || !cJSON_IsObject(arrayItem)) {
472         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object.";
473         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
474         return false;
475     }
476     cJSON *nameNode = cJSON_GetObjectItem(arrayItem, TAG_NAME.c_str());
477     if (!nameNode) {
478         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty.";
479         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
480         return false;
481     }
482     if (!cJSON_IsString(nameNode)) {
483         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string.";
484         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
485         return false;
486     }
487     values.push_back(nameNode->valuestring);
488 
489     cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
490     if (!valueNode) {
491         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << nameNode->valuestring;
492         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
493         return false;
494     }
495     if (!cJSON_IsString(valueNode)) {
496         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << nameNode->valuestring;
497         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
498         return false;
499     }
500     values.push_back(valueNode->valuestring);
501     return true;
502 }
503 
CheckPluralValue(const cJSON * arrayItem,const ResourceItem & resourceItem) const504 bool JsonCompiler::CheckPluralValue(const cJSON *arrayItem, const ResourceItem &resourceItem) const
505 {
506     if (!arrayItem || !cJSON_IsObject(arrayItem)) {
507         cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object.";
508         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
509         return false;
510     }
511     cJSON *quantityNode = cJSON_GetObjectItem(arrayItem, TAG_QUANTITY.c_str());
512     if (!quantityNode) {
513         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty.";
514         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
515         return false;
516     }
517     if (!cJSON_IsString(quantityNode)) {
518         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string.";
519         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
520         return false;
521     }
522     string quantityValue = quantityNode->valuestring;
523     if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) {
524         string buffer(" ");
525         for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) {
526                 buffer.append(iter).append(" ");
527             });
528         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
529         cerr << "' not in [" << buffer << "]." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
530         return false;
531     }
532 
533     cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
534     if (!valueNode) {
535         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
536         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
537         return false;
538     }
539     if (!cJSON_IsString(valueNode)) {
540         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
541         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
542         return false;
543     }
544     return true;
545 }
546 
CheckColorValue(const char * s) const547 bool JsonCompiler::CheckColorValue(const char *s) const
548 {
549     if (s == nullptr) {
550         return false;
551     }
552     // color regex
553     string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$";
554     if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) {
555         return true;
556     }
557     return false;
558 }
559 }
560 }
561 }
562