/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "json_compiler.h" #include <iostream> #include <regex> #include "restool_errors.h" namespace OHOS { namespace Global { namespace Restool { using namespace std; const string JsonCompiler::TAG_NAME = "name"; const string JsonCompiler::TAG_VALUE = "value"; const string JsonCompiler::TAG_PARENT = "parent"; const string JsonCompiler::TAG_QUANTITY = "quantity"; const vector<string> JsonCompiler::QUANTITY_ATTRS = { "zero", "one", "two", "few", "many", "other" }; JsonCompiler::JsonCompiler(ResType type, const string &output) : IResourceCompiler(type, output) { InitParser(); } JsonCompiler::~JsonCompiler() { } uint32_t JsonCompiler::CompileSingleFile(const FileInfo &fileInfo) { if (fileInfo.limitKey == "base" && fileInfo.fileCluster == "element" && fileInfo.filename == ID_DEFINED_FILE) { return RESTOOL_SUCCESS; } Json::Value root; if (!ResourceUtil::OpenJsonFile(fileInfo.filePath, root)) { return RESTOOL_ERROR; } if (!root.isObject()) { cerr << "Error: root node must object." << NEW_LINE_PATH << fileInfo.filePath << endl; return RESTOOL_ERROR; } if (root.getMemberNames().size() != 1) { cerr << "Error: root node must only one member." << NEW_LINE_PATH << fileInfo.filePath << endl; return RESTOOL_ERROR; } string tag = root.getMemberNames()[0]; auto ret = g_contentClusterMap.find(tag); if (ret == g_contentClusterMap.end()) { cerr << "Error: invalid tag name '" << tag << "'." << NEW_LINE_PATH << fileInfo.filePath << endl; return RESTOOL_ERROR; } FileInfo copy = fileInfo; copy.fileType = ret->second; if (!ParseJsonArrayLevel(root[tag], copy)) { return RESTOOL_ERROR; } return RESTOOL_SUCCESS; } // below private void JsonCompiler::InitParser() { using namespace placeholders; handles_.emplace(ResType::STRING, bind(&JsonCompiler::HandleString, this, _1, _2)); handles_.emplace(ResType::INTEGER, bind(&JsonCompiler::HandleInteger, this, _1, _2)); handles_.emplace(ResType::BOOLEAN, bind(&JsonCompiler::HandleBoolean, this, _1, _2)); handles_.emplace(ResType::COLOR, bind(&JsonCompiler::HandleColor, this, _1, _2)); handles_.emplace(ResType::FLOAT, bind(&JsonCompiler::HandleFloat, this, _1, _2)); handles_.emplace(ResType::STRARRAY, bind(&JsonCompiler::HandleStringArray, this, _1, _2)); handles_.emplace(ResType::INTARRAY, bind(&JsonCompiler::HandleIntegerArray, this, _1, _2)); handles_.emplace(ResType::THEME, bind(&JsonCompiler::HandleTheme, this, _1, _2)); handles_.emplace(ResType::PATTERN, bind(&JsonCompiler::HandlePattern, this, _1, _2)); handles_.emplace(ResType::PLURAL, bind(&JsonCompiler::HandlePlural, this, _1, _2)); } bool JsonCompiler::ParseJsonArrayLevel(const Json::Value &arrayNode, const FileInfo &fileInfo) { if (!arrayNode.isArray()) { cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' must be array."; cerr << NEW_LINE_PATH << fileInfo.filePath << endl; return false; } if (arrayNode.empty()) { cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' empty."; cerr << NEW_LINE_PATH << fileInfo.filePath << endl; return false; } for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) { if (!arrayNode[index].isObject()) { cerr << "Error: the seq=" << index << " item must be object." << NEW_LINE_PATH << fileInfo.filePath << endl; return false; } if (!ParseJsonObjectLevel(arrayNode[index], fileInfo)) { return false; } } return true; } bool JsonCompiler::ParseJsonObjectLevel(const Json::Value &objectNode, const FileInfo &fileInfo) { auto nameNode = objectNode[TAG_NAME]; if (nameNode.empty()) { cerr << "Error: name empty." << NEW_LINE_PATH << fileInfo.filePath << endl; return false; } if (!nameNode.isString()) { cerr << "Error: name must string." << NEW_LINE_PATH << fileInfo.filePath << endl; return false; } ResourceItem resourceItem(nameNode.asString(), fileInfo.keyParams, fileInfo.fileType); resourceItem.SetFilePath(fileInfo.filePath); resourceItem.SetLimitKey(fileInfo.limitKey); auto ret = handles_.find(fileInfo.fileType); if (ret == handles_.end()) { cerr << "Error: json parser don't support " << ResourceUtil::ResTypeToString(fileInfo.fileType) << endl; return false; } if (!ret->second(objectNode, resourceItem)) { return false; } return MergeResourceItem(resourceItem); } bool JsonCompiler::HandleString(const Json::Value &objectNode, ResourceItem &resourceItem) const { Json::Value valueNode = objectNode[TAG_VALUE]; if (!CheckJsonStringValue(valueNode, resourceItem)) { return false; } return PushString(valueNode.asString(), resourceItem); } bool JsonCompiler::HandleInteger(const Json::Value &objectNode, ResourceItem &resourceItem) const { Json::Value valueNode = objectNode[TAG_VALUE]; if (!CheckJsonIntegerValue(valueNode, resourceItem)) { return false; } return PushString(valueNode.asString(), resourceItem); } bool JsonCompiler::HandleBoolean(const Json::Value &objectNode, ResourceItem &resourceItem) const { Json::Value valueNode = objectNode[TAG_VALUE]; if (valueNode.isString()) { regex ref("^\\$(ohos:)?boolean:.*"); if (!regex_match(valueNode.asString(), ref)) { cerr << "Error: '" << valueNode.asString() << "' only refer '$boolean:xxx'."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } } else if (!valueNode.isBool()) { cerr << "Error: '" << resourceItem.GetName() << "' value not boolean."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return PushString(valueNode.asString(), resourceItem); } bool JsonCompiler::HandleColor(const Json::Value &objectNode, ResourceItem &resourceItem) const { return HandleString(objectNode, resourceItem); } bool JsonCompiler::HandleFloat(const Json::Value &objectNode, ResourceItem &resourceItem) const { return HandleString(objectNode, resourceItem); } bool JsonCompiler::HandleStringArray(const Json::Value &objectNode, ResourceItem &resourceItem) const { vector<string> extra; return ParseValueArray(objectNode, resourceItem, extra, [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool { if (!arrayItem.isObject()) { cerr << "Error: '" << resourceItem.GetName() << "' value array item not object."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } auto value = arrayItem[TAG_VALUE]; if (!CheckJsonStringValue(value, resourceItem)) { return false; } values.push_back(value.asString()); return true; }); } bool JsonCompiler::HandleIntegerArray(const Json::Value &objectNode, ResourceItem &resourceItem) const { vector<string> extra; return ParseValueArray(objectNode, resourceItem, extra, [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool { if (!CheckJsonIntegerValue(arrayItem, resourceItem)) { return false; } values.push_back(arrayItem.asString()); return true; }); } bool JsonCompiler::HandleTheme(const Json::Value &objectNode, ResourceItem &resourceItem) const { vector<string> extra; if (!ParseParent(objectNode, resourceItem, extra)) { return false; } return ParseValueArray(objectNode, resourceItem, extra, [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) { return ParseAttribute(arrayItem, resourceItem, values); }); } bool JsonCompiler::HandlePattern(const Json::Value &objectNode, ResourceItem &resourceItem) const { return HandleTheme(objectNode, resourceItem); } bool JsonCompiler::HandlePlural(const Json::Value &objectNode, ResourceItem &resourceItem) const { vector<string> extra; vector<string> attrs; bool result = ParseValueArray(objectNode, resourceItem, extra, [&attrs, this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) { if (!CheckPluralValue(arrayItem, resourceItem)) { return false; } string quantityValue = arrayItem[TAG_QUANTITY].asString(); if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue; cerr << "' duplicated." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } attrs.push_back(quantityValue); values.push_back(quantityValue); values.push_back(arrayItem[TAG_VALUE].asString()); return true; }); if (!result) { return false; } if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other'."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return true; } bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const { if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) { cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "'."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return true; } bool JsonCompiler::CheckJsonStringValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const { if (!valueNode.isString()) { cerr << "Error: '" << resourceItem.GetName() << "' value not string."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } const map<ResType, string> REFS = { { ResType::STRING, "\\$(ohos:)?string:" }, { ResType::STRARRAY, "\\$(ohos:)?string:" }, { ResType::COLOR, "\\$(ohos:)?color:" }, { ResType::FLOAT, "\\$(ohos:)?float:" } }; string value = valueNode.asString(); ResType type = resourceItem.GetResType(); if (type == ResType::COLOR && !CheckColorValue(value.c_str())) { string error = "invaild color value '" + value + \ "', only support refer '$color:xxx' or '#rgb','#argb','#rrggbb','#aarrggbb'."; cerr << "Error: " << error << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } regex ref("^\\$.+:"); smatch result; if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) { cerr << "Error: '" << value << "', only refer '"<< REFS.at(type) << "xxx'."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return true; } bool JsonCompiler::CheckJsonIntegerValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const { if (valueNode.isString()) { regex ref("^\\$(ohos:)?integer:.*"); if (!regex_match(valueNode.asString(), ref)) { cerr << "Error: '" << valueNode.asString() << "', only refer '$integer:xxx'."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } } else if (!valueNode.isInt()) { cerr << "Error: '" << resourceItem.GetName() << "' value not integer."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return true; } bool JsonCompiler::ParseValueArray(const Json::Value &objectNode, ResourceItem &resourceItem, const vector<string> &extra, HandleValue callback) const { Json::Value arrayNode = objectNode[TAG_VALUE]; if (!arrayNode.isArray()) { cerr << "Error: '" << resourceItem.GetName() << "' value not array."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (arrayNode.empty()) { cerr << "Error: '" << resourceItem.GetName() << "' value empty."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } vector<string> contents; if (!extra.empty()) { contents.assign(extra.begin(), extra.end()); } for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) { vector<string> values; if (!callback(arrayNode[index], resourceItem, values)) { return false; } contents.insert(contents.end(), values.begin(), values.end()); } string data = ResourceUtil::ComposeStrings(contents); if (data.empty()) { cerr << "Error: '" << resourceItem.GetName() << "' array too large."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return PushString(data, resourceItem); } bool JsonCompiler::ParseParent(const Json::Value &objectNode, const ResourceItem &resourceItem, vector<string> &extra) const { auto parent = objectNode[TAG_PARENT]; string type = ResourceUtil::ResTypeToString(resourceItem.GetResType()); if (!parent.isNull()) { if (!parent.isString()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (parent.empty()) { cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } string parentValue = parent.asString(); if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) { parentValue = "$" + parentValue; } else { parentValue = "$" + type + ":" + parentValue; } extra.push_back(parentValue); } return true; } bool JsonCompiler::ParseAttribute(const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) const { string type = ResourceUtil::ResTypeToString(resourceItem.GetResType()); if (!arrayItem.isObject()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } auto name = arrayItem[TAG_NAME]; if (name.empty()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (!name.isString()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } values.push_back(name.asString()); auto value = arrayItem[TAG_VALUE]; if (value.isNull()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString(); cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (!value.isString()) { cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString(); cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } values.push_back(value.asString()); return true; } bool JsonCompiler::CheckPluralValue(const Json::Value &arrayItem, const ResourceItem &resourceItem) const { if (!arrayItem.isObject()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } auto quantity = arrayItem[TAG_QUANTITY]; if (quantity.empty()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (!quantity.isString()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string."; cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } string quantityValue = quantity.asString(); if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) { string buffer(" "); for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) { buffer.append(iter).append(" "); }); cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue; cerr << "' not in [" << buffer << "]." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } auto value = arrayItem[TAG_VALUE]; if (value.isNull()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue; cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } if (!value.isString()) { cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue; cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl; return false; } return true; } bool JsonCompiler::CheckColorValue(const char *s) const { if (s == nullptr) { return false; } // color regex string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$"; if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) { return true; } return false; } } } }