• 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 (valueNode == nullptr) {
191         cerr << "Error: '" << resourceItem.GetName() << "' value not json.";
192         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
193         return false;
194     }
195     if (cJSON_IsString(valueNode)) {
196         regex ref("^\\$(ohos:)?boolean:.*");
197         if (!regex_match(valueNode->valuestring, ref)) {
198             cerr << "Error: '" << valueNode->valuestring << "' only refer '$boolean:xxx'.";
199             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
200             return false;
201         }
202         return PushString(valueNode->valuestring, resourceItem);
203     }
204     if (!cJSON_IsBool(valueNode)) {
205         cerr << "Error: '" << resourceItem.GetName() << "' value not boolean.";
206         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
207         return false;
208     }
209     return PushString(cJSON_IsTrue(valueNode) == 1 ? "true" : "false", resourceItem);
210 }
211 
HandleColor(const cJSON * objectNode,ResourceItem & resourceItem) const212 bool JsonCompiler::HandleColor(const cJSON *objectNode, ResourceItem &resourceItem) const
213 {
214     return HandleString(objectNode, resourceItem);
215 }
216 
HandleFloat(const cJSON * objectNode,ResourceItem & resourceItem) const217 bool JsonCompiler::HandleFloat(const cJSON *objectNode, ResourceItem &resourceItem) const
218 {
219     return HandleString(objectNode, resourceItem);
220 }
221 
HandleStringArray(const cJSON * objectNode,ResourceItem & resourceItem) const222 bool JsonCompiler::HandleStringArray(const cJSON *objectNode, ResourceItem &resourceItem) const
223 {
224     vector<string> extra;
225     return ParseValueArray(objectNode, resourceItem, extra,
226         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
227             if (!cJSON_IsObject(arrayItem)) {
228                 cerr << "Error: '" << resourceItem.GetName() << "' value array item not object.";
229                 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
230                 return false;
231             }
232             cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
233             if (!CheckJsonStringValue(valueNode, resourceItem)) {
234                 return false;
235             }
236             values.push_back(valueNode->valuestring);
237             return true;
238     });
239 }
240 
HandleIntegerArray(const cJSON * objectNode,ResourceItem & resourceItem) const241 bool JsonCompiler::HandleIntegerArray(const cJSON *objectNode, ResourceItem &resourceItem) const
242 {
243     vector<string> extra;
244     return ParseValueArray(objectNode, resourceItem, extra,
245         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
246             if (!CheckJsonIntegerValue(arrayItem, resourceItem)) {
247                 return false;
248             }
249             if (cJSON_IsString(arrayItem)) {
250                 values.push_back(arrayItem->valuestring);
251             } else {
252                 values.push_back(to_string(arrayItem->valueint));
253             }
254             return true;
255     });
256 }
257 
HandleTheme(const cJSON * objectNode,ResourceItem & resourceItem) const258 bool JsonCompiler::HandleTheme(const cJSON *objectNode, ResourceItem &resourceItem) const
259 {
260     vector<string> extra;
261     if (!ParseParent(objectNode, resourceItem, extra)) {
262         return false;
263     }
264     return ParseValueArray(objectNode, resourceItem, extra,
265         [this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
266             return ParseAttribute(arrayItem, resourceItem, values);
267         });
268 }
269 
HandlePattern(const cJSON * objectNode,ResourceItem & resourceItem) const270 bool JsonCompiler::HandlePattern(const cJSON *objectNode, ResourceItem &resourceItem) const
271 {
272     return HandleTheme(objectNode, resourceItem);
273 }
274 
HandlePlural(const cJSON * objectNode,ResourceItem & resourceItem) const275 bool JsonCompiler::HandlePlural(const cJSON *objectNode, ResourceItem &resourceItem) const
276 {
277     vector<string> extra;
278     vector<string> attrs;
279     bool result = ParseValueArray(objectNode, resourceItem, extra,
280         [&attrs, this](const cJSON *arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
281             if (!CheckPluralValue(arrayItem, resourceItem)) {
282                 return false;
283             }
284             cJSON *quantityNode = cJSON_GetObjectItem(arrayItem, TAG_QUANTITY.c_str());
285             if (!quantityNode || !cJSON_IsString(quantityNode)) {
286                 return false;
287             }
288             string quantityValue = quantityNode->valuestring;
289             if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) {
290                 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
291                 cerr << "' duplicated." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
292                 return false;
293             }
294             attrs.push_back(quantityValue);
295             values.push_back(quantityValue);
296             cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
297             if (!valueNode || !cJSON_IsString(valueNode)) {
298                 return false;
299             }
300             values.push_back(valueNode->valuestring);
301             return true;
302         });
303     if (!result) {
304         return false;
305     }
306     if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) {
307         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other'.";
308         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
309         return false;
310     }
311     return true;
312 }
313 
HandleSymbol(const cJSON * objectNode,ResourceItem & resourceItem) const314 bool JsonCompiler::HandleSymbol(const cJSON *objectNode, ResourceItem &resourceItem) const
315 {
316     cJSON *valueNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
317     if (!CheckJsonSymbolValue(valueNode, resourceItem)) {
318         return false;
319     }
320     return PushString(valueNode->valuestring, resourceItem);
321 }
322 
PushString(const string & value,ResourceItem & resourceItem) const323 bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const
324 {
325     if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) {
326         cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "'.";
327         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
328         return false;
329     }
330     return true;
331 }
332 
CheckJsonStringValue(const cJSON * valueNode,const ResourceItem & resourceItem) const333 bool JsonCompiler::CheckJsonStringValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
334 {
335     if (!valueNode || !cJSON_IsString(valueNode)) {
336         cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
337         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
338         return false;
339     }
340 
341     const map<ResType, string> REFS = {
342         { ResType::STRING, "\\$(ohos:)?string:" },
343         { ResType::STRARRAY, "\\$(ohos:)?string:" },
344         { ResType::COLOR, "\\$(ohos:)?color:" },
345         { ResType::FLOAT, "\\$(ohos:)?float:" }
346     };
347 
348     string value = valueNode->valuestring;
349     ResType type = resourceItem.GetResType();
350     if (type ==  ResType::COLOR && !CheckColorValue(value.c_str())) {
351         string error = "invalid color value '" + value + \
352                         "', only support refer '$color:xxx' or '#rgb','#argb','#rrggbb','#aarrggbb'.";
353         cerr << "Error: " << error << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
354         return false;
355     }
356     regex ref("^\\$.+:");
357     smatch result;
358     if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) {
359         cerr << "Error: '" << value << "', only refer '"<< REFS.at(type) << "xxx'.";
360         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
361         return false;
362     }
363     return true;
364 }
365 
CheckJsonIntegerValue(const cJSON * valueNode,const ResourceItem & resourceItem) const366 bool JsonCompiler::CheckJsonIntegerValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
367 {
368     if (!valueNode) {
369         cerr << "Error: '" << resourceItem.GetName() << "' value is empty";
370         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
371         return false;
372     }
373     if (cJSON_IsString(valueNode)) {
374         regex ref("^\\$(ohos:)?integer:.*");
375         if (!regex_match(valueNode->valuestring, ref)) {
376             cerr << "Error: '" << valueNode->valuestring << "', only refer '$integer:xxx'.";
377             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
378             return false;
379         }
380     } else if (!ResourceUtil::IsIntValue(valueNode)) {
381         cerr << "Error: '" << resourceItem.GetName() << "' value not integer.";
382         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
383         return false;
384     }
385     return true;
386 }
387 
CheckJsonSymbolValue(const cJSON * valueNode,const ResourceItem & resourceItem) const388 bool JsonCompiler::CheckJsonSymbolValue(const cJSON *valueNode, const ResourceItem &resourceItem) const
389 {
390     if (!valueNode || !cJSON_IsString(valueNode)) {
391         cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
392         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
393         return false;
394     }
395     string unicodeStr = valueNode->valuestring;
396     if (regex_match(unicodeStr, regex("^\\$(ohos:)?symbol:.*"))) {
397         return true;
398     }
399     int unicode = strtol(unicodeStr.c_str(), nullptr, 16);
400     if (!ResourceUtil::isUnicodeInPlane15or16(unicode)) {
401         cerr << "Error: '" << resourceItem.GetName() << "' value must in 0xF0000 ~ 0xFFFFF or 0x100000 ~ 0x10FFFF.";
402         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
403         return false;
404     }
405     return true;
406 }
407 
ParseValueArray(const cJSON * objectNode,ResourceItem & resourceItem,const vector<string> & extra,HandleValue callback) const408 bool JsonCompiler::ParseValueArray(const cJSON *objectNode, ResourceItem &resourceItem,
409                                    const vector<string> &extra, HandleValue callback) const
410 {
411     cJSON *arrayNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str());
412     if (arrayNode == nullptr) {
413         cerr << "Error: '" << resourceItem.GetName() << "' value not json.";
414         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
415         return false;
416     }
417 
418     if (!cJSON_IsArray(arrayNode)) {
419         cerr << "Error: '" << resourceItem.GetName() << "' value not array.";
420         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
421         return false;
422     }
423 
424     if (cJSON_GetArraySize(arrayNode) == 0) {
425         cerr << "Error: '" << resourceItem.GetName() << "' value empty.";
426         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
427         return false;
428     }
429 
430     vector<string> contents;
431     if (!extra.empty()) {
432         contents.assign(extra.begin(), extra.end());
433     }
434     for (cJSON *item = arrayNode->child; item; item = item->next) {
435         vector<string> values;
436         if (!callback(item, resourceItem, values)) {
437             return false;
438         }
439         contents.insert(contents.end(), values.begin(), values.end());
440     }
441 
442     string data = ResourceUtil::ComposeStrings(contents);
443     if (data.empty()) {
444         cerr << "Error: '" << resourceItem.GetName() << "' array too large.";
445         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
446         return false;
447     }
448     return PushString(data, resourceItem);
449 }
450 
ParseParent(const cJSON * objectNode,const ResourceItem & resourceItem,vector<string> & extra) const451 bool JsonCompiler::ParseParent(const cJSON *objectNode, const ResourceItem &resourceItem,
452                                vector<string> &extra) const
453 {
454     cJSON *parentNode = cJSON_GetObjectItem(objectNode, TAG_PARENT.c_str());
455     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
456     if (parentNode) {
457         if (!cJSON_IsString(parentNode)) {
458             cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string.";
459             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
460             return false;
461         }
462         string parentValue = parentNode->valuestring;
463         if (parentValue.empty()) {
464             cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty.";
465             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
466             return false;
467         }
468         if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) {
469             parentValue = "$" + parentValue;
470         } else {
471             parentValue = "$" + type + ":" + parentValue;
472         }
473         extra.push_back(parentValue);
474     }
475     return true;
476 }
477 
ParseAttribute(const cJSON * arrayItem,const ResourceItem & resourceItem,vector<string> & values) const478 bool JsonCompiler::ParseAttribute(const cJSON *arrayItem, const ResourceItem &resourceItem,
479                                   vector<string> &values) const
480 {
481     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
482     if (!arrayItem || !cJSON_IsObject(arrayItem)) {
483         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object.";
484         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
485         return false;
486     }
487     cJSON *nameNode = cJSON_GetObjectItem(arrayItem, TAG_NAME.c_str());
488     if (!nameNode) {
489         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty.";
490         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
491         return false;
492     }
493     if (!cJSON_IsString(nameNode)) {
494         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string.";
495         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
496         return false;
497     }
498     values.push_back(nameNode->valuestring);
499 
500     cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
501     if (!valueNode) {
502         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << nameNode->valuestring;
503         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
504         return false;
505     }
506     if (!cJSON_IsString(valueNode)) {
507         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << nameNode->valuestring;
508         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
509         return false;
510     }
511     values.push_back(valueNode->valuestring);
512     return true;
513 }
514 
CheckPluralValue(const cJSON * arrayItem,const ResourceItem & resourceItem) const515 bool JsonCompiler::CheckPluralValue(const cJSON *arrayItem, const ResourceItem &resourceItem) const
516 {
517     if (!arrayItem || !cJSON_IsObject(arrayItem)) {
518         cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object.";
519         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
520         return false;
521     }
522     cJSON *quantityNode = cJSON_GetObjectItem(arrayItem, TAG_QUANTITY.c_str());
523     if (!quantityNode) {
524         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty.";
525         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
526         return false;
527     }
528     if (!cJSON_IsString(quantityNode)) {
529         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string.";
530         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
531         return false;
532     }
533     string quantityValue = quantityNode->valuestring;
534     if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) {
535         string buffer(" ");
536         for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) {
537                 buffer.append(iter).append(" ");
538             });
539         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
540         cerr << "' not in [" << buffer << "]." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
541         return false;
542     }
543 
544     cJSON *valueNode = cJSON_GetObjectItem(arrayItem, TAG_VALUE.c_str());
545     if (!valueNode) {
546         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
547         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
548         return false;
549     }
550     if (!cJSON_IsString(valueNode)) {
551         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
552         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
553         return false;
554     }
555     return true;
556 }
557 
CheckColorValue(const char * s) const558 bool JsonCompiler::CheckColorValue(const char *s) const
559 {
560     if (s == nullptr) {
561         return false;
562     }
563     // color regex
564     string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$";
565     if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) {
566         return true;
567     }
568     return false;
569 }
570 }
571 }
572 }
573