diff --git a/BUILD.gn b/BUILD.gn index 3f045a9..b9b009b 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -44,6 +44,7 @@ ohos_executable("restool") { "src/restool.cpp", "src/select_compile_parse.cpp", "src/task_handle.cpp", + "src/translatable_parser.cpp", ] include_dirs = [ diff --git a/include/json_compiler.h b/include/json_compiler.h index cf6bb5a..f14ad9f 100644 --- a/include/json_compiler.h +++ b/include/json_compiler.h @@ -33,7 +33,7 @@ protected: private: void InitParser(); bool ParseJsonArrayLevel(const cJSON *arrayNode, const FileInfo &fileInfo); - bool ParseJsonObjectLevel(const cJSON *objectNode, const FileInfo &fileInfo); + bool ParseJsonObjectLevel(cJSON *objectNode, const FileInfo &fileInfo); using HandleResource = std::function; bool HandleString(const cJSON *objectNode, ResourceItem &resourceItem) const; @@ -62,11 +62,7 @@ private: bool CheckPluralValue(const cJSON *arrayItem, const ResourceItem &resourceItem) const; bool CheckColorValue(const char *s) const; std::map handles_; - static const std::string TAG_NAME; - static const std::string TAG_VALUE; - static const std::string TAG_PARENT; - static const std::string TAG_QUANTITY; - static const std::vector QUANTITY_ATTRS; + bool isBaseString_; cJSON *root_; }; } diff --git a/include/resource_data.h b/include/resource_data.h index 4113625..b595bc4 100644 --- a/include/resource_data.h +++ b/include/resource_data.h @@ -39,7 +39,7 @@ const static std::string NEW_LINE_PATH = "\nat "; const static std::string LONG_PATH_HEAD = "\\\\?\\"; const static int32_t VERSION_MAX_LEN = 128; const static int32_t INT_TO_BYTES = sizeof(uint32_t); -static const int8_t RESTOOL_VERSION[VERSION_MAX_LEN] = { "Restool 5.001" }; +static const int8_t RESTOOL_VERSION[VERSION_MAX_LEN] = { "Restool 5.002" }; const static int32_t TAG_LEN = 4; enum class KeyType { diff --git a/include/translatable_parser.h b/include/translatable_parser.h new file mode 100755 index 0000000..4b96be4 --- /dev/null +++ b/include/translatable_parser.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 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. + */ + +#ifndef OHOS_TRANSLATABLE_H +#define OHOS_TRANSLATABLE_H + +#include +#include "resource_data.h" + +namespace OHOS { +namespace Global { +namespace Restool { +class TranslatableParse { +public: + static bool ParseTranslatable(cJSON *objectNode, const FileInfo &fileInfo, const std::string &name); +private: + static bool ParseTranslatable(cJSON *objectNode, const std::string &filePath); + static bool CheckBaseStringAttr(const cJSON *objectNode); + static bool CheckBaseStringTranslatable(const cJSON *attrNode); + static bool CheckBaseStringPriority(const cJSON *attrNode); + static bool ReplaceTranslateTags(cJSON *node, const char *key); + static bool GetReplaceStringTranslate(std::string &str); + static bool FindTranslatePairs(const std::string &str, std::vector &posData); +}; +} +} +} +#endif \ No newline at end of file diff --git a/src/json_compiler.cpp b/src/json_compiler.cpp index ddca073..6b72a0e 100644 --- a/src/json_compiler.cpp +++ b/src/json_compiler.cpp @@ -18,19 +18,21 @@ #include #include #include "restool_errors.h" +#include "translatable_parser.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 JsonCompiler::QUANTITY_ATTRS = { "zero", "one", "two", "few", "many", "other" }; +const string TAG_NAME = "name"; +const string TAG_VALUE = "value"; +const string TAG_PARENT = "parent"; +const string TAG_QUANTITY = "quantity"; +const vector QUANTITY_ATTRS = { "zero", "one", "two", "few", "many", "other" }; +const vector TRANSLATION_TYPE = { "string", "strarray", "plural" }; JsonCompiler::JsonCompiler(ResType type, const string &output) - : IResourceCompiler(type, output), root_(nullptr) + : IResourceCompiler(type, output), isBaseString_(false), root_(nullptr) { InitParser(); } @@ -72,7 +74,8 @@ uint32_t JsonCompiler::CompileSingleFile(const FileInfo &fileInfo) cerr << NEW_LINE_PATH << fileInfo.filePath << endl; return RESTOOL_ERROR; } - + isBaseString_ = (fileInfo.limitKey == "base" && + find(TRANSLATION_TYPE.begin(), TRANSLATION_TYPE.end(), tag) != TRANSLATION_TYPE.end()); FileInfo copy = fileInfo; copy.fileType = ret->second; if (!ParseJsonArrayLevel(item, copy)) { @@ -125,7 +128,7 @@ bool JsonCompiler::ParseJsonArrayLevel(const cJSON *arrayNode, const FileInfo &f return true; } -bool JsonCompiler::ParseJsonObjectLevel(const cJSON *objectNode, const FileInfo &fileInfo) +bool JsonCompiler::ParseJsonObjectLevel(cJSON *objectNode, const FileInfo &fileInfo) { cJSON *nameNode = cJSON_GetObjectItem(objectNode, TAG_NAME.c_str()); if (!nameNode) { @@ -138,6 +141,9 @@ bool JsonCompiler::ParseJsonObjectLevel(const cJSON *objectNode, const FileInfo return false; } + if (isBaseString_ && !TranslatableParse::ParseTranslatable(objectNode, fileInfo, nameNode->valuestring)) { + return false; + } ResourceItem resourceItem(nameNode->valuestring, fileInfo.keyParams, fileInfo.fileType); resourceItem.SetFilePath(fileInfo.filePath); resourceItem.SetLimitKey(fileInfo.limitKey); diff --git a/src/translatable_parser.cpp b/src/translatable_parser.cpp new file mode 100755 index 0000000..8bf5cd4 --- /dev/null +++ b/src/translatable_parser.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024 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 "translatable_parser.h" +#include +#include +#include "resource_util.h" + +namespace OHOS { +namespace Global { +namespace Restool { +using namespace std; +const string TAG_VALUE = "value"; +const string TAG_ATTR = "attr"; +const string TAG_TRANSLATABLE = "translatable"; +const string TAG_PRIORITY = "priority"; +const string NO_TRANSLATE_START = "{noTranslateStart}"; +const string NO_TRANSLATE_END = "{noTranslateEnd}"; +const vector PRIORITY_ATTRS = { "code", "translate", "LT", "customer" }; + +bool TranslatableParse::ParseTranslatable(cJSON *objectNode, const FileInfo &fileInfo, const string &name) +{ + if (fileInfo.fileType == ResType::STRING) { + return ParseTranslatable(objectNode, fileInfo.filePath); + } + cJSON *arrayNode = cJSON_GetObjectItem(objectNode, TAG_VALUE.c_str()); + if (!arrayNode || !cJSON_IsArray(arrayNode)) { + cerr << "Error: '" << name << "' value not array."; + cerr << NEW_LINE_PATH << fileInfo.filePath << endl; + return false; + } + + if (cJSON_GetArraySize(arrayNode) == 0) { + cerr << "Error: '" << name << "' value empty."; + cerr << NEW_LINE_PATH << fileInfo.filePath << endl; + return false; + } + int32_t index = -1; + for (cJSON *item = arrayNode->child; item; item = item->next) { + index++; + if (!item || !cJSON_IsObject(item)) { + cerr << "Error: '" << name << "' value the seq=" << index << " item must be object."; + cerr << NEW_LINE_PATH << fileInfo.filePath << endl; + return false; + } + + if (!ParseTranslatable(item, fileInfo.filePath)) { + return false; + } + } + return true; +} + +bool TranslatableParse::ParseTranslatable(cJSON *objectNode, const string &filePath) +{ + if (!CheckBaseStringAttr(objectNode)) { + cerr << NEW_LINE_PATH << filePath << endl; + return false; + } + if (!ReplaceTranslateTags(objectNode, TAG_VALUE.c_str())) { + cerr << NEW_LINE_PATH << filePath << endl; + return false; + } + return true; +} + +bool TranslatableParse::CheckBaseStringAttr(const cJSON *objectNode) +{ + cJSON *attrNode = cJSON_GetObjectItem(objectNode, TAG_ATTR.c_str()); + if (!attrNode) { + return true; + } + + if (!cJSON_IsObject(attrNode)) { + cerr << "Error: attr node not obeject."; + return false; + } + return CheckBaseStringTranslatable(attrNode); +} + +bool TranslatableParse::CheckBaseStringTranslatable(const cJSON *attrNode) +{ + cJSON *translatableNode = cJSON_GetObjectItem(attrNode, TAG_TRANSLATABLE.c_str()); + if (!translatableNode) { + return CheckBaseStringPriority(attrNode); + } + if (!cJSON_IsBool(translatableNode)) { + cerr << "Error: invalid value for '" << TAG_TRANSLATABLE << "'. Must be a boolean."; + return false; + } + return CheckBaseStringPriority(attrNode); +} + +bool TranslatableParse::CheckBaseStringPriority(const cJSON *attrNode) +{ + cJSON *priorityNode = cJSON_GetObjectItem(attrNode, TAG_PRIORITY.c_str()); + if (!priorityNode) { + return true; + } + if (!cJSON_IsString(priorityNode)) { + cerr << "Error: invalid value for '" << TAG_PRIORITY << "'. Must be a string."; + return false; + } + string priorityValue = priorityNode->valuestring; + if (find(PRIORITY_ATTRS.begin(), PRIORITY_ATTRS.end(), priorityValue) == PRIORITY_ATTRS.end()) { + string message("[ "); + for (auto &value : PRIORITY_ATTRS) { + message.append("'" + value + "' "); + } + message.append("]"); + cerr << "Error: invalid value for '" << TAG_PRIORITY << "'. Must in " << message; + return false; + } + return true; +} + +bool TranslatableParse::ReplaceTranslateTags(cJSON *node, const char *key) +{ + cJSON *valueNode = cJSON_GetObjectItem(node, TAG_VALUE.c_str()); + if (!valueNode || !cJSON_IsString(valueNode)) { + cerr << "Error: value not string."; + return false; + } + + string value = valueNode->valuestring; + if (GetReplaceStringTranslate(value)) { + return cJSON_ReplaceItemInObject(node, key, cJSON_CreateString(value.c_str())); + } + return true; +} + +bool TranslatableParse::GetReplaceStringTranslate(string &str) +{ + vector posData; + if (!FindTranslatePairs(str, posData) || posData.empty()) { + return false; + } + size_t startIndex = 0; + size_t endIndex = 0; + string tmp; + for (size_t index = 0; index < posData.size(); index++) { + endIndex = posData[index]; + tmp.append(str, startIndex, endIndex - startIndex); + startIndex = posData[++index]; + } + str = tmp.append(str, posData.back()); + return true; +} + +bool TranslatableParse::FindTranslatePairs(const string &str, vector &posData) +{ + auto startPos = str.find(NO_TRANSLATE_START, 0); + auto endPos = str.find(NO_TRANSLATE_END, 0); + auto startLength = NO_TRANSLATE_START.length(); + auto endLength = NO_TRANSLATE_END.length(); + while (!(startPos == string::npos && endPos == string::npos)) { + if (startPos == string::npos || endPos == string::npos) { + return false; + } + if (startPos >= endPos || (!posData.empty() && posData.back() >= startPos)) { + return false; + } + posData.emplace_back(startPos); + posData.emplace_back(startPos + startLength); + posData.emplace_back(endPos); + posData.emplace_back(endPos + endLength); + startPos = str.find(NO_TRANSLATE_START, startPos + startLength); + endPos = str.find(NO_TRANSLATE_END, endPos + endLength); + } + return true; +} +} +} +}