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 handles_.emplace(ResType::SYMBOL, bind(&JsonCompiler::HandleSymbol, this, _1, _2));
94 }
95
ParseJsonArrayLevel(const Json::Value & arrayNode,const FileInfo & fileInfo)96 bool JsonCompiler::ParseJsonArrayLevel(const Json::Value &arrayNode, const FileInfo &fileInfo)
97 {
98 if (!arrayNode.isArray()) {
99 cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' must be array.";
100 cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
101 return false;
102 }
103
104 if (arrayNode.empty()) {
105 cerr << "Error: '" << ResourceUtil::ResTypeToString(fileInfo.fileType) << "' empty.";
106 cerr << NEW_LINE_PATH << fileInfo.filePath << endl;
107 return false;
108 }
109
110 for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) {
111 if (!arrayNode[index].isObject()) {
112 cerr << "Error: the seq=" << index << " item must be object." << NEW_LINE_PATH << fileInfo.filePath << endl;
113 return false;
114 }
115 if (!ParseJsonObjectLevel(arrayNode[index], fileInfo)) {
116 return false;
117 }
118 }
119 return true;
120 }
121
ParseJsonObjectLevel(const Json::Value & objectNode,const FileInfo & fileInfo)122 bool JsonCompiler::ParseJsonObjectLevel(const Json::Value &objectNode, const FileInfo &fileInfo)
123 {
124 auto nameNode = objectNode[TAG_NAME];
125 if (nameNode.empty()) {
126 cerr << "Error: name empty." << NEW_LINE_PATH << fileInfo.filePath << endl;
127 return false;
128 }
129
130 if (!nameNode.isString()) {
131 cerr << "Error: name must string." << NEW_LINE_PATH << fileInfo.filePath << endl;
132 return false;
133 }
134
135 ResourceItem resourceItem(nameNode.asString(), fileInfo.keyParams, fileInfo.fileType);
136 resourceItem.SetFilePath(fileInfo.filePath);
137 resourceItem.SetLimitKey(fileInfo.limitKey);
138 auto ret = handles_.find(fileInfo.fileType);
139 if (ret == handles_.end()) {
140 cerr << "Error: json parser don't support " << ResourceUtil::ResTypeToString(fileInfo.fileType) << endl;
141 return false;
142 }
143
144 if (!ret->second(objectNode, resourceItem)) {
145 return false;
146 }
147
148 return MergeResourceItem(resourceItem);
149 }
150
HandleString(const Json::Value & objectNode,ResourceItem & resourceItem) const151 bool JsonCompiler::HandleString(const Json::Value &objectNode, ResourceItem &resourceItem) const
152 {
153 Json::Value valueNode = objectNode[TAG_VALUE];
154 if (!CheckJsonStringValue(valueNode, resourceItem)) {
155 return false;
156 }
157 return PushString(valueNode.asString(), resourceItem);
158 }
159
HandleInteger(const Json::Value & objectNode,ResourceItem & resourceItem) const160 bool JsonCompiler::HandleInteger(const Json::Value &objectNode, ResourceItem &resourceItem) const
161 {
162 Json::Value valueNode = objectNode[TAG_VALUE];
163 if (!CheckJsonIntegerValue(valueNode, resourceItem)) {
164 return false;
165 }
166 return PushString(valueNode.asString(), resourceItem);
167 }
168
HandleBoolean(const Json::Value & objectNode,ResourceItem & resourceItem) const169 bool JsonCompiler::HandleBoolean(const Json::Value &objectNode, ResourceItem &resourceItem) const
170 {
171 Json::Value valueNode = objectNode[TAG_VALUE];
172 if (valueNode.isString()) {
173 regex ref("^\\$(ohos:)?boolean:.*");
174 if (!regex_match(valueNode.asString(), ref)) {
175 cerr << "Error: '" << valueNode.asString() << "' only refer '$boolean:xxx'.";
176 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
177 return false;
178 }
179 } else if (!valueNode.isBool()) {
180 cerr << "Error: '" << resourceItem.GetName() << "' value not boolean.";
181 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
182 return false;
183 }
184 return PushString(valueNode.asString(), resourceItem);
185 }
186
HandleColor(const Json::Value & objectNode,ResourceItem & resourceItem) const187 bool JsonCompiler::HandleColor(const Json::Value &objectNode, ResourceItem &resourceItem) const
188 {
189 return HandleString(objectNode, resourceItem);
190 }
191
HandleFloat(const Json::Value & objectNode,ResourceItem & resourceItem) const192 bool JsonCompiler::HandleFloat(const Json::Value &objectNode, ResourceItem &resourceItem) const
193 {
194 return HandleString(objectNode, resourceItem);
195 }
196
HandleStringArray(const Json::Value & objectNode,ResourceItem & resourceItem) const197 bool JsonCompiler::HandleStringArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
198 {
199 vector<string> extra;
200 return ParseValueArray(objectNode, resourceItem, extra,
201 [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
202 if (!arrayItem.isObject()) {
203 cerr << "Error: '" << resourceItem.GetName() << "' value array item not object.";
204 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
205 return false;
206 }
207 auto value = arrayItem[TAG_VALUE];
208 if (!CheckJsonStringValue(value, resourceItem)) {
209 return false;
210 }
211 values.push_back(value.asString());
212 return true;
213 });
214 }
215
HandleIntegerArray(const Json::Value & objectNode,ResourceItem & resourceItem) const216 bool JsonCompiler::HandleIntegerArray(const Json::Value &objectNode, ResourceItem &resourceItem) const
217 {
218 vector<string> extra;
219 return ParseValueArray(objectNode, resourceItem, extra,
220 [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) -> bool {
221 if (!CheckJsonIntegerValue(arrayItem, resourceItem)) {
222 return false;
223 }
224 values.push_back(arrayItem.asString());
225 return true;
226 });
227 }
228
HandleTheme(const Json::Value & objectNode,ResourceItem & resourceItem) const229 bool JsonCompiler::HandleTheme(const Json::Value &objectNode, ResourceItem &resourceItem) const
230 {
231 vector<string> extra;
232 if (!ParseParent(objectNode, resourceItem, extra)) {
233 return false;
234 }
235 return ParseValueArray(objectNode, resourceItem, extra,
236 [this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
237 return ParseAttribute(arrayItem, resourceItem, values);
238 });
239 }
240
HandlePattern(const Json::Value & objectNode,ResourceItem & resourceItem) const241 bool JsonCompiler::HandlePattern(const Json::Value &objectNode, ResourceItem &resourceItem) const
242 {
243 return HandleTheme(objectNode, resourceItem);
244 }
245
HandlePlural(const Json::Value & objectNode,ResourceItem & resourceItem) const246 bool JsonCompiler::HandlePlural(const Json::Value &objectNode, ResourceItem &resourceItem) const
247 {
248 vector<string> extra;
249 vector<string> attrs;
250 bool result = ParseValueArray(objectNode, resourceItem, extra,
251 [&attrs, this](const Json::Value &arrayItem, const ResourceItem &resourceItem, vector<string> &values) {
252 if (!CheckPluralValue(arrayItem, resourceItem)) {
253 return false;
254 }
255 string quantityValue = arrayItem[TAG_QUANTITY].asString();
256 if (find(attrs.begin(), attrs.end(), quantityValue) != attrs.end()) {
257 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
258 cerr << "' duplicated." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
259 return false;
260 }
261 attrs.push_back(quantityValue);
262 values.push_back(quantityValue);
263 values.push_back(arrayItem[TAG_VALUE].asString());
264 return true;
265 });
266 if (!result) {
267 return false;
268 }
269 if (find(attrs.begin(), attrs.end(), "other") == attrs.end()) {
270 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity must contains 'other'.";
271 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
272 return false;
273 }
274 return true;
275 }
276
HandleSymbol(const Json::Value & objectNode,ResourceItem & resourceItem) const277 bool JsonCompiler::HandleSymbol(const Json::Value &objectNode, ResourceItem &resourceItem) const
278 {
279 Json::Value valueNode = objectNode[TAG_VALUE];
280 if (!CheckJsonSymbolValue(valueNode, resourceItem)) {
281 return false;
282 }
283 return PushString(valueNode.asString(), resourceItem);
284 }
285
PushString(const string & value,ResourceItem & resourceItem) const286 bool JsonCompiler::PushString(const string &value, ResourceItem &resourceItem) const
287 {
288 if (!resourceItem.SetData(reinterpret_cast<const int8_t *>(value.c_str()), value.length())) {
289 cerr << "Error: resourceItem setdata fail,'" << resourceItem.GetName() << "'.";
290 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
291 return false;
292 }
293 return true;
294 }
295
CheckJsonStringValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const296 bool JsonCompiler::CheckJsonStringValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
297 {
298 if (!valueNode.isString()) {
299 cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
300 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
301 return false;
302 }
303
304 const map<ResType, string> REFS = {
305 { ResType::STRING, "\\$(ohos:)?string:" },
306 { ResType::STRARRAY, "\\$(ohos:)?string:" },
307 { ResType::COLOR, "\\$(ohos:)?color:" },
308 { ResType::FLOAT, "\\$(ohos:)?float:" }
309 };
310
311 string value = valueNode.asString();
312 ResType type = resourceItem.GetResType();
313 if (type == ResType::COLOR && !CheckColorValue(value.c_str())) {
314 string error = "invalid color value '" + value + \
315 "', only support refer '$color:xxx' or '#rgb','#argb','#rrggbb','#aarrggbb'.";
316 cerr << "Error: " << error << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
317 return false;
318 }
319 regex ref("^\\$.+:");
320 smatch result;
321 if (regex_search(value, result, ref) && !regex_match(result[0].str(), regex(REFS.at(type)))) {
322 cerr << "Error: '" << value << "', only refer '"<< REFS.at(type) << "xxx'.";
323 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
324 return false;
325 }
326 return true;
327 }
328
CheckJsonIntegerValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const329 bool JsonCompiler::CheckJsonIntegerValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
330 {
331 if (valueNode.isString()) {
332 regex ref("^\\$(ohos:)?integer:.*");
333 if (!regex_match(valueNode.asString(), ref)) {
334 cerr << "Error: '" << valueNode.asString() << "', only refer '$integer:xxx'.";
335 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
336 return false;
337 }
338 } else if (!valueNode.isInt()) {
339 cerr << "Error: '" << resourceItem.GetName() << "' value not integer.";
340 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
341 return false;
342 }
343 return true;
344 }
345
CheckJsonSymbolValue(const Json::Value & valueNode,const ResourceItem & resourceItem) const346 bool JsonCompiler::CheckJsonSymbolValue(const Json::Value &valueNode, const ResourceItem &resourceItem) const
347 {
348 if (!valueNode.isString()) {
349 cerr << "Error: '" << resourceItem.GetName() << "' value not string.";
350 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
351 return false;
352 }
353 string unicodeStr = valueNode.asString();
354 if (regex_match(unicodeStr, regex("^\\$(ohos:)?symbol:.*"))) {
355 return true;
356 }
357 int unicode = strtol(unicodeStr.c_str(), nullptr, 16);
358 if (!ResourceUtil::isUnicodeInPlane15or16(unicode)) {
359 cerr << "Error: '" << resourceItem.GetName() << "' value must in 0xF0000 ~ 0xFFFFF or 0x100000 ~ 0x10FFFF.";
360 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
361 return false;
362 }
363 return true;
364 }
365
ParseValueArray(const Json::Value & objectNode,ResourceItem & resourceItem,const vector<string> & extra,HandleValue callback) const366 bool JsonCompiler::ParseValueArray(const Json::Value &objectNode, ResourceItem &resourceItem,
367 const vector<string> &extra, HandleValue callback) const
368 {
369 Json::Value arrayNode = objectNode[TAG_VALUE];
370 if (!arrayNode.isArray()) {
371 cerr << "Error: '" << resourceItem.GetName() << "' value not array.";
372 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
373 return false;
374 }
375
376 if (arrayNode.empty()) {
377 cerr << "Error: '" << resourceItem.GetName() << "' value empty.";
378 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
379 return false;
380 }
381
382 vector<string> contents;
383 if (!extra.empty()) {
384 contents.assign(extra.begin(), extra.end());
385 }
386 for (Json::ArrayIndex index = 0; index < arrayNode.size(); index++) {
387 vector<string> values;
388 if (!callback(arrayNode[index], resourceItem, values)) {
389 return false;
390 }
391 contents.insert(contents.end(), values.begin(), values.end());
392 }
393
394 string data = ResourceUtil::ComposeStrings(contents);
395 if (data.empty()) {
396 cerr << "Error: '" << resourceItem.GetName() << "' array too large.";
397 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
398 return false;
399 }
400 return PushString(data, resourceItem);
401 }
402
ParseParent(const Json::Value & objectNode,const ResourceItem & resourceItem,vector<string> & extra) const403 bool JsonCompiler::ParseParent(const Json::Value &objectNode, const ResourceItem &resourceItem,
404 vector<string> &extra) const
405 {
406 auto parent = objectNode[TAG_PARENT];
407 string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
408 if (!parent.isNull()) {
409 if (!parent.isString()) {
410 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' parent not string.";
411 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
412 return false;
413 }
414 if (parent.empty()) {
415 cerr << "Error: " << type << " '"<< resourceItem.GetName() << "' parent empty.";
416 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
417 return false;
418 }
419 string parentValue = parent.asString();
420 if (regex_match(parentValue, regex("^ohos:" + type + ":.+"))) {
421 parentValue = "$" + parentValue;
422 } else {
423 parentValue = "$" + type + ":" + parentValue;
424 }
425 extra.push_back(parentValue);
426 }
427 return true;
428 }
429
ParseAttribute(const Json::Value & arrayItem,const ResourceItem & resourceItem,vector<string> & values) const430 bool JsonCompiler::ParseAttribute(const Json::Value &arrayItem, const ResourceItem &resourceItem,
431 vector<string> &values) const
432 {
433 string type = ResourceUtil::ResTypeToString(resourceItem.GetResType());
434 if (!arrayItem.isObject()) {
435 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute not object.";
436 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
437 return false;
438 }
439 auto name = arrayItem[TAG_NAME];
440 if (name.empty()) {
441 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name empty.";
442 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
443 return false;
444 }
445 if (!name.isString()) {
446 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute name not string.";
447 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
448 return false;
449 }
450 values.push_back(name.asString());
451
452 auto value = arrayItem[TAG_VALUE];
453 if (value.isNull()) {
454 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
455 cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
456 return false;
457 }
458 if (!value.isString()) {
459 cerr << "Error: " << type << " '" << resourceItem.GetName() << "' attribute '" << name.asString();
460 cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
461 return false;
462 }
463 values.push_back(value.asString());
464 return true;
465 }
466
CheckPluralValue(const Json::Value & arrayItem,const ResourceItem & resourceItem) const467 bool JsonCompiler::CheckPluralValue(const Json::Value &arrayItem, const ResourceItem &resourceItem) const
468 {
469 if (!arrayItem.isObject()) {
470 cerr << "Error: Plural '" << resourceItem.GetName() << "' array item not object.";
471 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
472 return false;
473 }
474 auto quantity = arrayItem[TAG_QUANTITY];
475 if (quantity.empty()) {
476 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity empty.";
477 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
478 return false;
479 }
480 if (!quantity.isString()) {
481 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity not string.";
482 cerr << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
483 return false;
484 }
485 string quantityValue = quantity.asString();
486 if (find(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), quantityValue) == QUANTITY_ATTRS.end()) {
487 string buffer(" ");
488 for_each(QUANTITY_ATTRS.begin(), QUANTITY_ATTRS.end(), [&buffer](auto iter) {
489 buffer.append(iter).append(" ");
490 });
491 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
492 cerr << "' not in [" << buffer << "]." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
493 return false;
494 }
495
496 auto value = arrayItem[TAG_VALUE];
497 if (value.isNull()) {
498 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
499 cerr << "' value empty." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
500 return false;
501 }
502 if (!value.isString()) {
503 cerr << "Error: Plural '" << resourceItem.GetName() << "' quantity '" << quantityValue;
504 cerr << "' value not string." << NEW_LINE_PATH << resourceItem.GetFilePath() << endl;
505 return false;
506 }
507 return true;
508 }
509
CheckColorValue(const char * s) const510 bool JsonCompiler::CheckColorValue(const char *s) const
511 {
512 if (s == nullptr) {
513 return false;
514 }
515 // color regex
516 string regColor = "^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$";
517 if (regex_match(s, regex("^\\$.*")) || regex_match(s, regex(regColor))) {
518 return true;
519 }
520 return false;
521 }
522 }
523 }
524 }
525