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