• 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." << NEW_LINE_PATH << fileInfo.filePath << endl;
56         return RESTOOL_ERROR;
57     }
58 
59     if (root.getMemberNames().size() != 1) {
60         cerr << "Error: root node must only one member." << NEW_LINE_PATH << 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 << "'." << NEW_LINE_PATH << 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 << NEW_LINE_PATH << fileInfo.filePath << endl;
100         return false;
101     }
102 
103     if (arrayNode.empty()) {
104         cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' empty.";
105         cerr << NEW_LINE_PATH << 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." << NEW_LINE_PATH << 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." << NEW_LINE_PATH << fileInfo.filePath << endl;
126         return false;
127     }
128 
129     if (!nameNode.isString()) {
130         cerr << "Error: name must string." << NEW_LINE_PATH << 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 << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
176             return false;
177         }
178     } else if (!valueNode.isBool()) {
179         cerr << "Error: '" << resourceItem.GetName() << "' value not boolean.";
180         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
181         return false;
182     }
183     return PushString(valueNode.asString(), resourceItem);
184 }
185 
HandleColor(const Json::Value & objectNode,ResourceItem & resourceItem) const186 bool JsonCompiler::HandleColor(const Json::Value &objectNode, ResourceItem &resourceItem) const
187 {
188     return HandleString(objectNode, resourceItem);
189 }
190 
HandleFloat(const Json::Value & objectNode,ResourceItem & resourceItem) const191 bool JsonCompiler::HandleFloat(const Json::Value &objectNode, ResourceItem &resourceItem) const
192 {
193     return HandleString(objectNode, resourceItem);
194 }
195 
HandleStringArray(const Json::Value & objectNode,ResourceItem & resourceItem) const196 bool JsonCompiler::HandleStringArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
197 {
198     vector<string> extra;
199     return ParseValueArray(objectNode, resourceItem, extra,
200         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
201             if (!arrayItem.isObject()) {
202                 cerr << "Error: '" << resourceItem.GetName() << "' value array item not object.";
203                 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
204                 return false;
205             }
206             auto value = arrayItem[TAG_VALUE];
207             if (!CheckJsonStringValue(value, resourceItem)) {
208                 return false;
209             }
210             values.push_back(value.asString());
211             return true;
212     });
213 }
214 
HandleIntegerArray(const Json::Value & objectNode,ResourceItem & resourceItem) const215 bool JsonCompiler::HandleIntegerArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
216 {
217     vector<string> extra;
218     return ParseValueArray(objectNode, resourceItem, extra,
219         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
220             if (!CheckJsonIntegerValue(arrayItem, resourceItem)) {
221                 return false;
222             }
223             values.push_back(arrayItem.asString());
224             return true;
225     });
226 }
227 
HandleTheme(const Json::Value & objectNode,ResourceItem & resourceItem) const228 bool JsonCompiler::HandleTheme(const Json::Value &objectNode, ResourceItem &resourceItem) const
229 {
230     vector<string> extra;
231     if (!ParseParent(objectNode, resourceItem, extra)) {
232         return false;
233     }
234     return ParseValueArray(objectNode, resourceItem, extra,
235         [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
236             return ParseAttribute(arrayItem, resourceItem, values);
237         });
238 }
239 
HandlePattern(const Json::Value & objectNode,ResourceItem & resourceItem) const240 bool JsonCompiler::HandlePattern(const Json::Value &objectNode, ResourceItem &resourceItem) const
241 {
242     return HandleTheme(objectNode, resourceItem);
243 }
244 
HandlePlural(const Json::Value & objectNode,ResourceItem & resourceItem) const245 bool JsonCompiler::HandlePlural(const Json::Value &objectNode, ResourceItem &resourceItem) const
246 {
247     vector<string> extra;
248     vector<string> attrs;
249     bool result = ParseValueArray(objectNode, resourceItem, extra,
250         [&attrs, this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
251             if (!CheckPluralValue(arrayItem, resourceItem)) {
252                 return false;
253             }
254             string quantityValue = arrayItem[TAG_QUANTITY].asString();
255             if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) {
256                 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
257                 cerr << "' duplicated." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
258                 return false;
259             }
260             attrs.push_back(quantityValue);
261             values.push_back(quantityValue);
262             values.push_back(arrayItem[TAG_VALUE].asString());
263             return true;
264         });
265     if (!result) {
266         return false;
267     }
268     if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) {
269         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other'.";
270         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
271         return false;
272     }
273     return true;
274 }
275 
PushString(const string & value,ResourceItem & resourceItem) const276 bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const
277 {
278     if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) {
279         cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "'.";
280         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
281         return false;
282     }
283     return true;
284 }
285 
CheckJsonStringValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const286 bool JsonCompiler::CheckJsonStringValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
287 {
288     if (!valueNode.isString()) {
289         cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
290         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
291         return false;
292     }
293 
294     const map<ResType, string> REFS = {
295         { ResType::STRING, "\\$(ohos:)?string:" },
296         { ResType::STRARRAY, "\\$(ohos:)?string:" },
297         { ResType::COLOR, "\\$(ohos:)?color:" },
298         { ResType::FLOAT, "\\$(ohos:)?float:" }
299     };
300 
301     string value = valueNode.asString();
302     ResType type = resourceItem.GetResType();
303     if (type ==  ResType::COLOR && !CheckColorValue(value.c_str())) {
304         string error = "invaild color value '" + value + \
305                         "', only support refer '$color:xxx' or '#rgb','#argb','#rrggbb','#aarrggbb'.";
306         cerr << "Error: " << error << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
307         return false;
308     }
309     regex ref("^\\$.+:");
310     smatch result;
311     if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) {
312         cerr << "Error: '" << value << "', only refer '"<< REFS.at(type) << "xxx'.";
313         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
314         return false;
315     }
316     return true;
317 }
318 
CheckJsonIntegerValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const319 bool JsonCompiler::CheckJsonIntegerValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
320 {
321     if (valueNode.isString()) {
322         regex ref("^\\$(ohos:)?integer:.*");
323         if (!regex_match(valueNode.asString(), ref)) {
324             cerr << "Error: '" << valueNode.asString() << "', only refer '$integer:xxx'.";
325             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
326             return false;
327         }
328     } else if (!valueNode.isInt()) {
329         cerr << "Error: '" << resourceItem.GetName() << "' value not integer.";
330         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
331         return false;
332     }
333     return true;
334 }
335 
ParseValueArray(const Json::Value & objectNode,ResourceItem & resourceItem,const vector<string> & extra,HandleValue callback) const336 bool JsonCompiler::ParseValueArray(const Json::Value &objectNode, ResourceItem &resourceItem,
337                                    const vector<string> &extra, HandleValue callback) const
338 {
339     Json::Value arrayNode = objectNode[TAG_VALUE];
340     if (!arrayNode.isArray()) {
341         cerr << "Error: '" << resourceItem.GetName() << "' value not array.";
342         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
343         return false;
344     }
345 
346     if (arrayNode.empty()) {
347         cerr << "Error: '" << resourceItem.GetName() << "' value empty.";
348         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
349         return false;
350     }
351 
352     vector<string> contents;
353     if (!extra.empty()) {
354         contents.assign(extra.begin(), extra.end());
355     }
356     for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) {
357         vector<string> values;
358         if (!callback(arrayNode[index], resourceItem, values)) {
359             return false;
360         }
361         contents.insert(contents.end(), values.begin(), values.end());
362     }
363 
364     string data = ResourceUtil::ComposeStrings(contents);
365     if (data.empty()) {
366         cerr << "Error: '" << resourceItem.GetName() << "' array too large.";
367         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
368         return false;
369     }
370     return PushString(data, resourceItem);
371 }
372 
ParseParent(const Json::Value & objectNode,const ResourceItem & resourceItem,vector<string> & extra) const373 bool JsonCompiler::ParseParent(const Json::Value &objectNode, const ResourceItem &resourceItem,
374                                vector<string> &extra) const
375 {
376     auto parent = objectNode[TAG_PARENT];
377     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
378     if (!parent.isNull()) {
379         if (!parent.isString()) {
380             cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string.";
381             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
382             return false;
383         }
384         if (parent.empty()) {
385             cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty.";
386             cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
387             return false;
388         }
389         string parentValue = parent.asString();
390         if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) {
391             parentValue = "$" + parentValue;
392         } else {
393             parentValue = "$" + type + ":" + parentValue;
394         }
395         extra.push_back(parentValue);
396     }
397     return true;
398 }
399 
ParseAttribute(const Json::Value & arrayItem,const ResourceItem & resourceItem,vector<string> & values) const400 bool JsonCompiler::ParseAttribute(const Json::Value &arrayItem, const ResourceItem &resourceItem,
401                                   vector<string> &values) const
402 {
403     string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
404     if (!arrayItem.isObject()) {
405         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object.";
406         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
407         return false;
408     }
409     auto name = arrayItem[TAG_NAME];
410     if (name.empty()) {
411         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty.";
412         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
413         return false;
414     }
415     if (!name.isString()) {
416         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string.";
417         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
418         return false;
419     }
420     values.push_back(name.asString());
421 
422     auto value = arrayItem[TAG_VALUE];
423     if (value.isNull()) {
424         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
425         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
426         return false;
427     }
428     if (!value.isString()) {
429         cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
430         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
431         return false;
432     }
433     values.push_back(value.asString());
434     return true;
435 }
436 
CheckPluralValue(const Json::Value & arrayItem,const ResourceItem & resourceItem) const437 bool JsonCompiler::CheckPluralValue(const Json::Value &arrayItem, const ResourceItem &resourceItem) const
438 {
439     if (!arrayItem.isObject()) {
440         cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object.";
441         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
442         return false;
443     }
444     auto quantity = arrayItem[TAG_QUANTITY];
445     if (quantity.empty()) {
446         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty.";
447         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
448         return false;
449     }
450     if (!quantity.isString()) {
451         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string.";
452         cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
453         return false;
454     }
455     string quantityValue = quantity.asString();
456     if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) {
457         string buffer(" ");
458         for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) {
459                 buffer.append(iter).append(" ");
460             });
461         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
462         cerr << "' not in [" << buffer << "]." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
463         return false;
464     }
465 
466     auto value = arrayItem[TAG_VALUE];
467     if (value.isNull()) {
468         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
469         cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
470         return false;
471     }
472     if (!value.isString()) {
473         cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
474         cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
475         return false;
476     }
477     return true;
478 }
479 
CheckColorValue(const char * s) const480 bool JsonCompiler::CheckColorValue(const char *s) const
481 {
482     if (s == nullptr) {
483         return false;
484     }
485     // color regex
486     string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$";
487     if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) {
488         return true;
489     }
490     return false;
491 }
492 }
493 }
494 }
495