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