• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "json_compiler.h"
17 #include <iostream>
18 #include <regex>
19 #include "restool_errors.h"
20 
21 namespace OHOS {
22 namespace Global {
23 namespace Restool {
24 using namespace std;
25 const string JsonCompiler::TAG_NAME = "name";
26 const string JsonCompiler::TAG_VALUE = "value";
27 const string JsonCompiler::TAG_PARENT = "parent";
28 const string JsonCompiler::TAG_QUANTITY = "quantity";
29 const vector<string> JsonCompiler::QUANTITY_ATTRS = { "zero", "one", "two", "few", "many", "other" };
30 
JsonCompiler(ResType type,const string & output)31 JsonCompiler::JsonCompiler(ResType type, const string &output)
32     : IResourceCompiler(type, output)
33 {
34     InitParser();
35 }
36 
~JsonCompiler()37 JsonCompiler::~JsonCompiler()
38 {
39 }
40 
CompileSingleFile(const FileInfo & fileInfo)41 uint32_t JsonCompiler::CompileSingleFile(const FileInfo &fileInfo)
42 {
43     if (fileInfo.limitKey == "base" &&
44         fileInfo.fileCluster == "element" &&
45         fileInfo.filename == ID_DEFINED_FILE) {
46         return RESTOOL_SUCCESS;
47     }
48 
49     Json::Value root;
50     if (!ResourceUtil::OpenJsonFile(fileInfo.filePath, root)) {
51         return RESTOOL_ERROR;
52     }
53 
54     if (!root.isObject()) {
55         cerr << "Error: root node must object," << fileInfo.filePath << endl;
56         return RESTOOL_ERROR;
57     }
58 
59     if (root.getMemberNames().size() != 1) {
60         cerr << "Error: root node must only one member," << fileInfo.filePath << endl;
61         return RESTOOL_ERROR;
62     }
63 
64     string tag = root.getMemberNames()[0];
65     auto ret = g_contentClusterMap.find(tag);
66     if (ret == g_contentClusterMap.end()) {
67         cerr << "Error: invalid tag name '" << tag << "'," << fileInfo.filePath << endl;
68         return RESTOOL_ERROR;
69     }
70 
71     FileInfo copy = fileInfo;
72     copy.fileType = ret->second;
73     if (!ParseJsonArrayLevel(root[tag], copy)) {
74         return RESTOOL_ERROR;
75     }
76     return RESTOOL_SUCCESS;
77 }
78 
79 // below private
InitParser()80 void JsonCompiler::InitParser()
81 {
82     using namespace placeholders;
83     handles_.emplace(ResType::STRING, bind(&JsonCompiler::HandleString, this, _1, _2));
84     handles_.emplace(ResType::INTEGER, bind(&JsonCompiler::HandleInteger, this, _1, _2));
85     handles_.emplace(ResType::BOOLEAN, bind(&JsonCompiler::HandleBoolean, this, _1, _2));
86     handles_.emplace(ResType::COLOR, bind(&JsonCompiler::HandleColor, this, _1, _2));
87     handles_.emplace(ResType::FLOAT, bind(&JsonCompiler::HandleFloat, this, _1, _2));
88     handles_.emplace(ResType::STRARRAY, bind(&JsonCompiler::HandleStringArray, this, _1, _2));
89     handles_.emplace(ResType::INTARRAY, bind(&JsonCompiler::HandleIntegerArray, this, _1, _2));
90     handles_.emplace(ResType::THEME, bind(&JsonCompiler::HandleTheme, this, _1, _2));
91     handles_.emplace(ResType::PATTERN, bind(&JsonCompiler::HandlePattern, this, _1, _2));
92     handles_.emplace(ResType::PLURAL, bind(&JsonCompiler::HandlePlural, this, _1, _2));
93 }
94 
ParseJsonArrayLevel(const Json::Value & arrayNode,const FileInfo & fileInfo)95 bool JsonCompiler::ParseJsonArrayLevel(const Json::Value &arrayNode, const FileInfo &fileInfo)
96 {
97     if (!arrayNode.isArray()) {
98         cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' must be array,";
99         cerr << fileInfo.filePath << endl;
100         return false;
101     }
102 
103     if (arrayNode.empty()) {
104         cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' empty,";
105         cerr << fileInfo.filePath << endl;
106         return false;
107     }
108 
109     for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) {
110         if (!arrayNode[index].isObject()) {
111             cerr << "Error: the seq=" << index << " item must be object," << fileInfo.filePath << endl;
112             return false;
113         }
114         if (!ParseJsonObjectLevel(arrayNode[index], fileInfo)) {
115             return false;
116         }
117     }
118     return true;
119 }
120 
ParseJsonObjectLevel(const Json::Value & objectNode,const FileInfo & fileInfo)121 bool JsonCompiler::ParseJsonObjectLevel(const Json::Value &objectNode, const FileInfo &fileInfo)
122 {
123     auto nameNode = objectNode[TAG_NAME];
124     if (nameNode.empty()) {
125         cerr << "Error: name empty," << fileInfo.filePath << endl;
126         return false;
127     }
128 
129     if (!nameNode.isString()) {
130         cerr << "Error: name must string," << fileInfo.filePath << endl;
131         return false;
132     }
133 
134     ResourceItem resourceItem(nameNode.asString(), fileInfo.keyParams, fileInfo.fileType);
135     resourceItem.SetFilePath(fileInfo.filePath);
136     resourceItem.SetLimitKey(fileInfo.limitKey);
137     auto ret = handles_.find(fileInfo.fileType);
138     if (ret == handles_.end()) {
139         cerr << "Error: json parser don't support " << ResourceUtil::ResTypeToString(fileInfo.fileType) << endl;
140         return false;
141     }
142 
143     if (!ret->second(objectNode, resourceItem)) {
144         return false;
145     }
146 
147     return MergeResourceItem(resourceItem);
148 }
149 
HandleString(const Json::Value & objectNode,ResourceItem & resourceItem) const150 bool JsonCompiler::HandleString(const Json::Value &objectNode, ResourceItem &resourceItem) const
151 {
152     Json::Value valueNode = objectNode[TAG_VALUE];
153     if (!CheckJsonStringValue(valueNode, resourceItem)) {
154         return false;
155     }
156     return PushString(valueNode.asString(), resourceItem);
157 }
158 
HandleInteger(const Json::Value & objectNode,ResourceItem & resourceItem) const159 bool JsonCompiler::HandleInteger(const Json::Value &objectNode, ResourceItem &resourceItem) const
160 {
161     Json::Value valueNode = objectNode[TAG_VALUE];
162     if (!CheckJsonIntegerValue(valueNode, resourceItem)) {
163         return false;
164     }
165     return PushString(valueNode.asString(), resourceItem);
166 }
167 
HandleBoolean(const Json::Value & objectNode,ResourceItem & resourceItem) const168 bool JsonCompiler::HandleBoolean(const Json::Value &objectNode, ResourceItem &resourceItem) const
169 {
170     Json::Value valueNode = objectNode[TAG_VALUE];
171     if (valueNode.isString()) {
172         regex ref("^\\$(ohos:)?boolean:.*");
173         if (!regex_match(valueNode.asString(), ref)) {
174             cerr << "Error: '" << valueNode.asString() << "' only refer '$boolean:xxx',";
175             cerr << resourceItem.GetFilePath() << endl;
176             return false;
177         }
178     } else if (!valueNode.isBool()) {
179         cerr << "Error: '" << resourceItem.GetName() << "' value not boolean," << resourceItem.GetFilePath() << endl;
180         return false;
181     }
182     return PushString(valueNode.asString(), resourceItem);
183 }
184 
HandleColor(const Json::Value & objectNode,ResourceItem & resourceItem) const185 bool JsonCompiler::HandleColor(const Json::Value &objectNode, ResourceItem &resourceItem) const
186 {
187     return HandleString(objectNode, resourceItem);
188 }
189 
HandleFloat(const Json::Value & objectNode,ResourceItem & resourceItem) const190 bool JsonCompiler::HandleFloat(const Json::Value &objectNode, ResourceItem &resourceItem) const
191 {
192     return HandleString(objectNode, resourceItem);
193 }
194 
HandleStringArray(const Json::Value & objectNode,ResourceItem & resourceItem) const195 bool JsonCompiler::HandleStringArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
196 {
197     vector<string> extra;
198     return ParseValueArray(objectNode, resourceItem, extra,
199         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
200             if (!arrayItem.isObject()) {
201                 cerr << "Error: '" << resourceItem.GetName() << "' value array item not object,";
202                 cerr << resourceItem.GetFilePath() << endl;
203                 return false;
204             }
205             auto value = arrayItem[TAG_VALUE];
206             if (!CheckJsonStringValue(value, resourceItem)) {
207                 return false;
208             }
209             values.push_back(value.asString());
210             return true;
211     });
212 }
213 
HandleIntegerArray(const Json::Value & objectNode,ResourceItem & resourceItem) const214 bool JsonCompiler::HandleIntegerArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
215 {
216     vector<string> extra;
217     return ParseValueArray(objectNode, resourceItem, extra,
218         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
219             if (!CheckJsonIntegerValue(arrayItem, resourceItem)) {
220                 return false;
221             }
222             values.push_back(arrayItem.asString());
223             return true;
224     });
225 }
226 
HandleTheme(const Json::Value & objectNode,ResourceItem & resourceItem) const227 bool JsonCompiler::HandleTheme(const Json::Value &objectNode, ResourceItem &resourceItem) const
228 {
229     vector<string> extra;
230     if (!ParseParent(objectNode, resourceItem, extra)) {
231         return false;
232     }
233     return ParseValueArray(objectNode, resourceItem, extra,
234         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
235             return ParseAttribute(arrayItem, resourceItem, values);
236         });
237 }
238 
HandlePattern(const Json::Value & objectNode,ResourceItem & resourceItem) const239 bool JsonCompiler::HandlePattern(const Json::Value &objectNode, ResourceItem &resourceItem) const
240 {
241     return HandleTheme(objectNode, resourceItem);
242 }
243 
HandlePlural(const Json::Value & objectNode,ResourceItem & resourceItem) const244 bool JsonCompiler::HandlePlural(const Json::Value &objectNode, ResourceItem &resourceItem) const
245 {
246     vector<string> extra;
247     vector<string> attrs;
248     bool result = ParseValueArray(objectNode, resourceItem, extra,
249         [&attrs, this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
250             if (!CheckPluralValue(arrayItem, resourceItem)) {
251                 return false;
252             }
253             string quantityValue = arrayItem[TAG_QUANTITY].asString();
254             if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) {
255                 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
256                 cerr << "' duplicated," << resourceItem.GetFilePath() << endl;
257                 return false;
258             }
259             attrs.push_back(quantityValue);
260             values.push_back(quantityValue);
261             values.push_back(arrayItem[TAG_VALUE].asString());
262             return true;
263         });
264     if (!result) {
265         return false;
266     }
267     if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) {
268         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other',";
269         cerr << resourceItem.GetFilePath() << endl;
270         return false;
271     }
272     return true;
273 }
274 
PushString(const string & value,ResourceItem & resourceItem) const275 bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const
276 {
277     if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) {
278         cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "',";
279         cerr << resourceItem.GetFilePath() << endl;
280         return false;
281     }
282     return true;
283 }
284 
CheckJsonStringValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const285 bool JsonCompiler::CheckJsonStringValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
286 {
287     if (!valueNode.isString()) {
288         cerr << "Error: '" << resourceItem.GetName() << "' value not string," << resourceItem.GetFilePath() << endl;
289         return false;
290     }
291 
292     const map<ResType, string> REFS = {
293         { ResType::STRING, "\\$(ohos:)?string:" },
294         { ResType::STRARRAY, "\\$(ohos:)?string:" },
295         { ResType::COLOR, "\\$(ohos:)?color:" },
296         { ResType::FLOAT, "\\$(ohos:)?float:" }
297     };
298 
299     string value = valueNode.asString();
300     ResType type = resourceItem.GetResType();
301     if (type ==  ResType::COLOR && !CheckColorValue(value.c_str())) {
302         string error = "invaild color '" + value + "', in " + resourceItem.GetFilePath();
303         cerr << "Error: " << error << endl;
304         return false;
305     }
306     regex ref("^\\$.+:");
307     smatch result;
308     if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) {
309         cerr << "Error: '" << value << "' only refer '"<< REFS.at(type) << "xxx',";
310         cerr << resourceItem.GetFilePath() << endl;
311         return false;
312     }
313     return true;
314 }
315 
CheckJsonIntegerValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const316 bool JsonCompiler::CheckJsonIntegerValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
317 {
318     if (valueNode.isString()) {
319         regex ref("^\\$(ohos:)?integer:.*");
320         if (!regex_match(valueNode.asString(), ref)) {
321             cerr << "Error: '" << valueNode.asString() << "' only refer '$integer:xxx',";
322             cerr << resourceItem.GetFilePath() << endl;
323             return false;
324         }
325     } else if (!valueNode.isInt()) {
326         cerr << "Error: '" << resourceItem.GetName() << "' value not integer," << resourceItem.GetFilePath() << endl;
327         return false;
328     }
329     return true;
330 }
331 
ParseValueArray(const Json::Value & objectNode,ResourceItem & resourceItem,const vector<string> & extra,HandleValue callback) const332 bool JsonCompiler::ParseValueArray(const Json::Value &objectNode, ResourceItem &resourceItem,
333                                    const vector<string> &extra, HandleValue callback) const
334 {
335     Json::Value arrayNode = objectNode[TAG_VALUE];
336     if (!arrayNode.isArray()) {
337         cerr << "Error: '" << resourceItem.GetName() << "' value not array," << resourceItem.GetFilePath() << endl;
338         return false;
339     }
340 
341     if (arrayNode.empty()) {
342         cerr << "Error: '" << resourceItem.GetName() << "' value empty," << resourceItem.GetFilePath() << endl;
343         return false;
344     }
345 
346     vector<string> contents;
347     if (!extra.empty()) {
348         contents.assign(extra.begin(), extra.end());
349     }
350     for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) {
351         vector<string> values;
352         if (!callback(arrayNode[index], resourceItem, values)) {
353             return false;
354         }
355         contents.insert(contents.end(), values.begin(), values.end());
356     }
357 
358     string data = ResourceUtil::ComposeStrings(contents);
359     if (data.empty()) {
360         cerr << "Error: '" << resourceItem.GetName() << "' array too large,"<< resourceItem.GetFilePath() << endl;
361         return false;
362     }
363     return PushString(data, resourceItem);
364 }
365 
ParseParent(const Json::Value & objectNode,const ResourceItem & resourceItem,vector<string> & extra) const366 bool JsonCompiler::ParseParent(const Json::Value &objectNode, const ResourceItem &resourceItem,
367                                vector<string> &extra) const
368 {
369     auto parent = objectNode[TAG_PARENT];
370     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
371     if (!parent.isNull()) {
372         if (!parent.isString()) {
373             cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string,";
374             cerr << resourceItem.GetFilePath() << endl;
375             return false;
376         }
377         if (parent.empty()) {
378             cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty,";
379             cerr << resourceItem.GetFilePath() << endl;
380             return false;
381         }
382         string parentValue = parent.asString();
383         if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) {
384             parentValue = "$" + parentValue;
385         } else {
386             parentValue = "$" + type + ":" + parentValue;
387         }
388         extra.push_back(parentValue);
389     }
390     return true;
391 }
392 
ParseAttribute(const Json::Value & arrayItem,const ResourceItem & resourceItem,vector<string> & values) const393 bool JsonCompiler::ParseAttribute(const Json::Value &arrayItem, const ResourceItem &resourceItem,
394                                   vector<string> &values) const
395 {
396     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
397     if (!arrayItem.isObject()) {
398         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object,";
399         cerr << resourceItem.GetFilePath() << endl;
400         return false;
401     }
402     auto name = arrayItem[TAG_NAME];
403     if (name.empty()) {
404         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty,";
405         cerr << resourceItem.GetFilePath() << endl;
406         return false;
407     }
408     if (!name.isString()) {
409         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string,";
410         cerr << resourceItem.GetFilePath() << endl;
411         return false;
412     }
413     values.push_back(name.asString());
414 
415     auto value = arrayItem[TAG_VALUE];
416     if (value.isNull()) {
417         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
418         cerr << "' value empty," << resourceItem.GetFilePath() << endl;
419         return false;
420     }
421     if (!value.isString()) {
422         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
423         cerr << "' value not string," << resourceItem.GetFilePath() << endl;
424         return false;
425     }
426     values.push_back(value.asString());
427     return true;
428 }
429 
CheckPluralValue(const Json::Value & arrayItem,const ResourceItem & resourceItem) const430 bool JsonCompiler::CheckPluralValue(const Json::Value &arrayItem, const ResourceItem &resourceItem) const
431 {
432     if (!arrayItem.isObject()) {
433         cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object,";
434         cerr << resourceItem.GetFilePath() << endl;
435         return false;
436     }
437     auto quantity = arrayItem[TAG_QUANTITY];
438     if (quantity.empty()) {
439         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty,";
440         cerr << resourceItem.GetFilePath() << endl;
441         return false;
442     }
443     if (!quantity.isString()) {
444         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string,";
445         cerr << resourceItem.GetFilePath() << endl;
446         return false;
447     }
448     string quantityValue = quantity.asString();
449     if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) {
450         string buffer(" ");
451         for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) {
452                 buffer.append(iter).append(" ");
453             });
454         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
455         cerr << "' not in [" << buffer << "]," << resourceItem.GetFilePath() << endl;
456         return false;
457     }
458 
459     auto value = arrayItem[TAG_VALUE];
460     if (value.isNull()) {
461         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
462         cerr << "' value empty" << resourceItem.GetFilePath() << endl;
463         return false;
464     }
465     if (!value.isString()) {
466         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
467         cerr << "' value not string" << resourceItem.GetFilePath() << endl;
468         return false;
469     }
470     return true;
471 }
472 
CheckColorValue(const char * s) const473 bool JsonCompiler::CheckColorValue(const char *s) const
474 {
475     if (s == nullptr) {
476         return false;
477     }
478     // color regex
479     string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$";
480     if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) {
481         return true;
482     }
483     return false;
484 }
485 }
486 }
487 }
488