• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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