1 // OpenLieroX
2
3 #include <assert.h>
4 #include <algorithm>
5 #include "CssParser.h"
6
7 #include "StringUtils.h"
8 #include "Timer.h"
9 #include "FindFile.h"
10
11 //
12 // Css parser class
13 //
14
15 /////////////////////////
16 // Set the attribute value and parse it
setValue(const std::string & value)17 void CSSParser::Attribute::setValue(const std::string &value)
18 {
19 // Remove multiple blank characters and turn all blank characters to spaces
20 StringBuf val = value;
21
22 // Check if the value is function-like (for example url(some path.png) or rgb(0 , 50, 0)
23 if (val.str().find('(') != std::string::npos && val.str().find(')') != std::string::npos) {
24 std::string func_name = val.readUntil('(');
25 TrimSpaces(func_name);
26
27 // Check the name
28 bool is_valid_name = true;
29 for (std::string::iterator it = func_name.begin(); it != func_name.end(); it++)
30 if (!isalnum((uchar)*it) && *it != '_' && *it != '-')
31 is_valid_name = false;
32
33 if (is_valid_name) {
34 tParsedValue.push_back(Value(value)); // Just put the whole "function" as a first (and only) value
35 return;
36 }
37 // Not a function, proceed further
38 }
39
40 // Parse the value
41 std::vector<std::string> tokens = val.splitByBlank();
42
43 for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); it++) {
44
45 // String value
46 tParsedValue.push_back(Value(*it));
47 }
48
49 sValue = val.str();
50 }
51
52 /////////////////////
53 // Returns the value represented as a string, converts it to lower case
getString(const std::string & def) const54 std::string CSSParser::Attribute::Value::getString(const std::string& def) const
55 {
56 if (sValue.size())
57 return stringtolower(sValue);
58 else
59 return def;
60 }
61
62 /////////////////////
63 // Returns the value represented as float
getFloat(float def) const64 float CSSParser::Attribute::Value::getFloat(float def) const
65 {
66 bool fail = false;
67 float res = from_string<float>(sValue, fail);
68 if (fail)
69 return def;
70 else
71 return res;
72 }
73
74 /////////////////////
75 // Returns the value represented as integer
getInteger(int def) const76 int CSSParser::Attribute::Value::getInteger(int def) const
77 {
78 bool fail = false;
79 int res = from_string<int>(sValue, fail);
80 if (fail)
81 return def;
82 else
83 return res;
84 }
85
86 /////////////////////
87 // Returns the value represented as boolean
getBool(bool def) const88 bool CSSParser::Attribute::Value::getBool(bool def) const
89 {
90 if (stringcaseequal(sValue, "true") || stringcaseequal(sValue, "yes") || sValue == "1")
91 return true;
92 else if (stringcaseequal(sValue, "false") || stringcaseequal(sValue, "no") || sValue == "0")
93 return false;
94 else
95 return def;
96 }
97
98 /////////////////////
99 // Returns the value represented as URL
getURL(const std::string & def) const100 std::string CSSParser::Attribute::Value::getURL(const std::string& def) const
101 {
102 std::string res = sValue;
103 StripQuotes(res); // Get rid of quotes
104
105 // Check if it is url()
106 if (res.size() >= 5) {
107 if (stringcaseequal(res.substr(0, 3), "url")) {
108 if (res[3] == '(' && *res.rbegin() == ')') {
109 res.erase(0, 4);
110 res.erase(res.size() - 1);
111 StripQuotes(res); // Strip the quotes if any
112 }
113 }
114 }
115
116 if (res.size())
117 return res;
118 else
119 return def;
120 }
121
122 /////////////////////
123 // Returns the value represented as a color
getColor(const Color & def) const124 Color CSSParser::Attribute::Value::getColor(const Color& def) const
125 {
126 bool fail = false;
127 Color res = StrToCol(sValue, fail);
128 if (fail)
129 return def;
130 else
131 return res;
132 }
133
134 /////////////////////
135 // Returns the value's unit
getUnit(const std::string & def) const136 std::string CSSParser::Attribute::Value::getUnit(const std::string& def) const
137 {
138 // Must be an integer/float value
139 bool fail1 = false, fail2 = false;
140 from_string<int>(sValue, fail1);
141 from_string<float>(sValue, fail2);
142 if (fail1 && fail2)
143 return def;
144
145 // Get the unit
146 std::string res;
147 for (std::string::const_reverse_iterator it = sValue.rbegin(); it != sValue.rend(); it++) {
148 if (isdigit((uchar)*it) || *it == '.')
149 break;
150 res += tolower((uchar)*it);
151 }
152 std::reverse(res.begin(), res.end());
153
154 if (res.size())
155 return res;
156 else
157 return def;
158 }
159
160 ////////////////////
161 // Compare operator for Context
operator ==(const CSSParser::Selector::Context & c2) const162 bool CSSParser::Selector::Context::operator ==(const CSSParser::Selector::Context &c2) const
163 {
164 // Must have same size
165 if (getSize() != c2.getSize())
166 return false;
167
168 // Must have same context selectors
169 std::list<CSSParser::Selector>::const_iterator it1 = tContextSelectors.begin();
170 std::list<CSSParser::Selector>::const_iterator it2 = c2.tContextSelectors.begin();
171 for (; it1 != tContextSelectors.end(); it1++, it2++)
172 if (!(*it1 == *it2))
173 return false;
174
175 return true;
176 }
177
178 ////////////////////
179 // Returns true if this is part of context c2
isPartOf(const CSSParser::Selector::Context & c2) const180 bool CSSParser::Selector::Context::isPartOf(const CSSParser::Selector::Context& c2) const
181 {
182 // c2 has bo be wider (or at least equal) to contain us
183 if (getSize() > c2.getSize())
184 return false;
185
186 // Must have same context selectors
187 std::list<CSSParser::Selector>::const_reverse_iterator it1 = tContextSelectors.rbegin();
188 std::list<CSSParser::Selector>::const_reverse_iterator it2 = c2.tContextSelectors.rbegin();
189 for (; it2 != c2.tContextSelectors.rend(); it1++, it2++)
190 if (!(*it1 == *it2))
191 return false;
192
193 return true;
194 }
195
196 /////////////////////
197 // Compare operator for two selectors
operator ==(const CSSParser::Selector & s2) const198 bool CSSParser::Selector::operator ==(const CSSParser::Selector &s2) const
199 {
200 // If there's some ID, ignore everything else
201 if (sID.size())
202 return sID == s2.sID && sPseudoClass == s2.sPseudoClass;
203
204 // If there's element and class, compare everything
205 return sElement == s2.sElement && sClass == s2.sClass && sPseudoClass == s2.sPseudoClass && tContext == s2.tContext;
206 }
207
208 ////////////////////////////
209 // Returns true if the Selector s2 should inherit our attributes
isParentOf(const CSSParser::Selector & s2) const210 bool CSSParser::Selector::isParentOf(const CSSParser::Selector& s2) const
211 {
212 /*bool psClassOK = (sPseudoClass.size() == 0) || (sPseudoClass == s2.sPseudoClass);
213 if (sID.size())
214 return psClassOK && sID == s2.sID;
215 bool classOK = (sClass.size() == 0) || (sClass == s2.sClass);
216
217 if (s2.sID.size()) {
218 return ((s2.sElement == sElement) || s2.sElement.size() == 0) && classOK && psClassOK;
219 }
220
221 return ((sElement == s2.sElement) || sElement.size() == 0) && classOK && psClassOK;*/
222 // The priority is the following:
223 // Pseudoclass
224 // ID
225 // Class
226 // Element
227
228 if (sPseudoClass.size() != 0 && s2.sPseudoClass.size() == 0)
229 return false;
230 if (sPseudoClass != s2.sPseudoClass && sPseudoClass.size() != 0)
231 return false;
232
233 if (sID.size() != 0 && s2.sID.size() == 0)
234 return false;
235 if (sID != s2.sID && sID.size() != 0)
236 return false;
237
238 if (sClass.size() != 0 && s2.sClass.size() == 0)
239 return false;
240 if (sClass != s2.sClass && sClass.size() != 0)
241 return false;
242
243 if (sElement.size() != 0 && s2.sElement.size() == 0)
244 return false;
245 if (sElement != s2.sElement && sElement.size() != 0)
246 return false;
247
248 return true;
249 }
250
251 /////////////////////////
252 // Finds an attribute based on its name
findAttribute(const std::string & name)253 CSSParser::Attribute *CSSParser::Selector::findAttribute(const std::string& name)
254 {
255 for (std::list<Attribute>::iterator it = tAttributes.begin(); it != tAttributes.end(); it++)
256 if (it->getName() == name)
257 return &(*it);
258
259 return NULL;
260 }
261
262 /////////////////////////
263 // Merges this selector with another one (adds attributes that are missing in this selector)
inheritFrom(const CSSParser::Selector & s2)264 void CSSParser::Selector::inheritFrom(const CSSParser::Selector &s2)
265 {
266 for (std::list<Attribute>::const_iterator it = s2.tAttributes.begin(); it != s2.tAttributes.end(); it++) {
267 CSSParser::Attribute *a = findAttribute(it->getName());
268 if (a == NULL) // The attribute does not exist, add it
269 addAttribute(*it);
270 }
271 }
272
273 ////////////////////////////
274 // Add an attribute to the selector
addAttribute(const CSSParser::Attribute & a)275 void CSSParser::Selector::addAttribute(const CSSParser::Attribute& a)
276 {
277 Attribute *attr = findAttribute(a.getName());
278 if (attr)
279 *attr = a; // Overwrite it
280 else
281 tAttributes.push_back(a);
282 }
283
284 ////////////////////////
285 // Get the error message nicely formatted
getFullMessage() const286 std::string CSSParser::Error::getFullMessage() const
287 {
288 return "Parse error on line " + to_string<size_t>(iLine) + ": " + sMessage;
289 }
290
291 /////////////////////////
292 // Throws a parse error, if fatal is true, the parsing will stop
throwError(const std::string & msg,bool fatal)293 void CSSParser::throwError(const std::string &msg, bool fatal)
294 {
295 tParseErrors.push_back(Error(iLine, msg, fatal));
296 if (fatal)
297 throw Error(iLine, msg, fatal);
298 }
299
300
301 //////////////////////////
302 // Read a selector
readSelector()303 void CSSParser::readSelector()
304 {
305 // Selector ends with an {
306 StringBuf s = tCss.readUntil('{');
307 if (tCss.atEnd())
308 throwError("Unexpected end of file found", true);
309
310 // Trim any spaces
311 s.trimBlank();
312
313 // Unnamed selector?
314 if (s.empty()) {
315 throwError("Unnamed selector found, \"default\" assumed");
316 s = "default";
317 }
318
319 // There can be more selectors delimited with commas, for example:
320 // TEXTBOX, IMAGE, MINIMAP { border: 1px solid black; }
321 std::list<Selector> selectors;
322
323 std::vector<std::string> tokens = s.splitBy(',');
324 for (std::vector<std::string>::iterator it = tokens.begin(); it != tokens.end(); it++) {
325 // Initialize variables
326 StringBuf selector_str = *it;
327 selector_str.trimBlank();
328
329 // Empty one?
330 if (selector_str.empty()) {
331 throwError("Unnamed selector found, \"default\" assumed");
332 selectors.push_back(Selector("default", "", "", "", sCSSPath));
333 continue;
334 }
335
336 // The selector can have so called context delimited by spaces, for example:
337 // DIALOG LISTVIEW SCROLLBAR
338 std::vector<std::string> context = selector_str.splitByBlank();
339 size_t i = 0;
340
341 Selector current_sel;
342 current_sel.setFilePos(tSelectors.size() + selectors.size());
343 for (std::vector<std::string>::iterator c_it = context.begin(); c_it != context.end(); c_it++, i++) {
344
345 StringBuf cont = *c_it;
346
347
348 // If the selector starts with a sharp (#), it means an ID
349 if (cont.getC() == '#') {
350 cont.incPos(); // Skip the sharp
351
352 // Also split by ':' in case of a pseudo class (#selector:pseudoclass)
353 if (i == context.size() - 1) { // The last element is the real selector
354 current_sel.setID(cont.readUntil(':'));
355 current_sel.setPseudoClass(stringtolower(cont.getRestStr()));
356 } else
357 current_sel.addContextSelector(Selector("", cont.readUntil(':'), "", stringtolower(cont.getRestStr()), sCSSPath));
358
359 continue;
360 }
361 cont.resetPos();
362
363 // If the selector starts with a dot (.), it means a pure class
364 if (cont.getC() == '.') {
365 cont.incPos(); // Skip the dot
366
367 // Also split by ':' in case of a pseudo class (.selector:pseudoclass)
368 if (i == context.size() - 1) { // The last element is the real selector
369 current_sel.setClass(cont.readUntil(':'));
370 current_sel.setPseudoClass(stringtolower(cont.getRestStr()));
371 } else
372 current_sel.addContextSelector(Selector("", "", cont.readUntil(':'), stringtolower(cont.getRestStr()), sCSSPath));
373
374 continue;
375 }
376 cont.resetPos();
377
378 // Normal selector
379 if (i == context.size() - 1) { // The last element is the real selector
380 // Can have this format:
381 // SELECTOR.CLASS:PSEUDOCLASS
382 if (cont.str().find('.') != std::string::npos) {
383 current_sel.setElement(stringtolower(cont.readUntil('.')));
384 current_sel.setClass(cont.readUntil(':'));
385 } else
386 current_sel.setElement(stringtolower(cont.readUntil(':')));
387 current_sel.setPseudoClass(stringtolower(cont.getRestStr()));
388 } else
389 current_sel.addContextSelector(Selector(stringtolower(cont.readUntil('.')), "", cont.readUntil(':'), stringtolower(cont.getRestStr()), sCSSPath));
390 }
391
392 // Add the selector
393 selectors.push_back(current_sel);
394 }
395
396 // Read the attributes and add them to all the selectors we've read
397 StringBuf attributes = tCss.readUntil('}');
398 while (!attributes.atEnd()) {
399 const Attribute& a = readAttribute(attributes);
400 for (std::list<Selector>::iterator s_it = selectors.begin(); s_it != selectors.end(); s_it++)
401 s_it->addAttribute(a);
402
403 // Skip any blank space after the attribute
404 // This avoids getting a blank attribute if there are some blank characters after the last
405 // attribute (i.e. the attribute before } )
406 attributes.skipBlank();
407 }
408
409 // Add the selectors to the global selector list
410 for (std::list<Selector>::iterator i = selectors.begin(); i != selectors.end(); i++)
411 addSelector(*i);
412
413 // Skip any blank spaces after the selector
414 tCss.skipBlank();
415 }
416
417 /////////////////////
418 // Read an attribute
readAttribute(StringBuf & buf)419 CSSParser::Attribute CSSParser::readAttribute(StringBuf& buf)
420 {
421 // An attribute can ends with a semicolon
422 StringBuf attr = buf.readUntil(';');
423
424 // Trim
425 attr.trimBlank();
426
427 // Check for !important
428 bool important = false;
429 for(size_t i = 0; !attr.atEnd(); ++i, attr.incPos()) {
430 if (attr.getC() == '!') {
431 size_t len = 1;
432 len += attr.skipBlank(); // Skip any blank characters between ! and "important"
433 std::string im = attr.read(9); // Read the "important" string (9 = strlen(important))
434 len += 9;
435 if (stringcaseequal(im, "important")) { // Found?
436 important = true;
437 attr.erase(i, len);
438 break;
439 }
440 }
441 }
442 attr.resetPos();
443
444 // Split to name and value (delimited by ':')
445 StringBuf name = attr.readUntil(':');
446 StringBuf value = attr.getRestStr();
447
448 // Adjust
449 name.trimBlank();
450 name.toLower();
451 value.trimBlank();
452
453 // Fill in the attribute
454 return Attribute(name.str(), value.str(), CSS_PRIORITY, important);
455 }
456
457 ///////////////////////
458 // Find a selector by a name
findSelector(const Selector & another)459 CSSParser::Selector *CSSParser::findSelector(const Selector &another)
460 {
461 for (std::list<Selector>::iterator it = tSelectors.begin(); it != tSelectors.end(); it++)
462 if (*it == another)
463 return &(*it);
464
465 return NULL;
466 }
467
468 ///////////////////////
469 // Add a selector to the list
addSelector(CSSParser::Selector & s)470 void CSSParser::addSelector(CSSParser::Selector &s)
471 {
472 // Add it
473 tSelectors.push_back(s);
474 }
475
476 ////////////////////
477 // Sort predicate to get the correct inheritance order
parent_sort_pred(CSSParser::Selector s1,CSSParser::Selector s2)478 bool parent_sort_pred(CSSParser::Selector s1, CSSParser::Selector s2)
479 {
480 if (s1 == s2)
481 return s1.getFilePos() < s2.getFilePos();
482 return s2.isParentOf(s1);
483 }
484
485 //////////////////////////
486 // Returns a style for the specified element
getStyleForElement(const std::string & element,const std::string & id,const std::string & cl,const std::string & pscl,const Selector::Context & context) const487 CSSParser::Selector CSSParser::getStyleForElement(const std::string& element, const std::string& id,
488 const std::string& cl, const std::string& pscl, const Selector::Context& context) const
489 {
490 // We have to know what element we are looking for
491 assert(element.size() != 0);
492
493 // Create the resulting selector
494 Selector result(element, id, cl, pscl, sCSSPath);
495 result.setContext(context);
496
497 // Go through the selectors and check, if we can inherit attributes from it
498 std::vector<Selector> parents;
499 for (std::list<Selector>::const_iterator it = tSelectors.begin(); it != tSelectors.end(); it++) {
500 if (it->isParentOf(result)) {
501 Selector tmp;
502 tmp = *it;
503 tmp.setElement(element);
504 parents.push_back(tmp);
505 }
506 }
507
508 // Sort the parents by their specialization
509 //std::sort(parents.begin(), parents.end(), parent_sort_pred);
510 bool sorted = false;
511 while (!sorted) {
512 sorted = true;
513 for (int i=0; i < (int)parents.size() - 1; i++) {
514 if (parents[i + 1].isParentOf(parents[i])) {
515 Selector tmp = parents[i];
516 parents[i] = parents[i+1];
517 parents[i+1] = tmp;
518 sorted = false;
519 }
520 }
521 }
522
523 // Inherit the values
524 for (std::vector<Selector>::reverse_iterator it = parents.rbegin(); it != parents.rend(); it++)
525 result.inheritFrom(*it);
526
527 return result;
528 }
529
530 /////////////////////
531 // Removes CSS comments from the string being parsed
removeComments()532 void CSSParser::removeComments()
533 {
534 while (true) {
535 // Erase everything between /* and */
536 size_t comment_start = tCss.str().find("/*");
537 if (comment_start == std::string::npos)
538 break;
539 size_t comment_end = tCss.str().find("*/", comment_start);
540 tCss.erase(comment_start, comment_end - comment_start + 2);
541 }
542 }
543
544 /////////////////
545 // Clear the parser
clear()546 void CSSParser::clear()
547 {
548 tCss = "";
549 tParseErrors.clear();
550 tSelectors.clear();
551 sCSSPath = "";
552 iLine = 0;
553 }
554
555 ///////////////////
556 // Main parsing function
557 // HINT: nothing is cleared here, the caller is responsible for clearing the parser if necessary
parse(const std::string & css,const std::string & path)558 bool CSSParser::parse(const std::string &css, const std::string& path)
559 {
560
561 // Init the variables
562 tCss = css;
563 sCSSPath = path;
564
565
566 // Remove comments from the file
567 removeComments();
568
569 // Read all the selectors
570 try {
571 while (!tCss.atEnd())
572 readSelector();
573 } catch (...) {
574 tCss = "";
575 return false;
576 }
577 tCss = "";
578
579 return true; // Successfully parsed
580 }
581
582 /////////////////////
583 // Parses the part inside the given selector, for example:
584 // <tag style="the css here will be parsed with this function">
parseInSelector(CSSParser::Selector & sel,const std::string & css,size_t priority)585 bool CSSParser::parseInSelector(CSSParser::Selector &sel, const std::string &css, size_t priority)
586 {
587 // Read the attributes and add them to all the selectors we've read
588 StringBuf attributes = css;
589 try {
590 while (!attributes.atEnd()) {
591 Attribute a = readAttribute(attributes);
592 a.setPriority(priority);
593 sel.addAttribute(a);
594
595 // Skip any blank space after the attribute
596 // This avoids getting a blank attribute if there are some blank characters after the last
597 // attribute (i.e. the attribute before } )
598 attributes.skipBlank();
599 }
600 } catch (...) {
601 return false;
602 }
603
604 return true;
605 }
606
test_css()607 void CSSParser::test_css()
608 {
609 return;
610 AbsTime start = GetTime();
611
612 CSSParser c;
613 c.parse(GetFileContents("default.css"), ".");
614
615 printf("==== CSS TEST ====\n");
616 printf("Selectors in the file: \n");
617 /*for (std::list<CSSParser::Selector>::const_iterator it = c.getSelectors().begin();
618 it != c.getSelectors().end(); it++) {
619 // Element info
620 printf(" ");
621 if (it->getElement().size())
622 printf("Element: " + it->getElement() + " ");
623 if (it->getClass().size())
624 printf("Class: " + it->getClass() + " ");
625 if (it->getID().size())
626 printf("ID: " + it->getID() + " ");
627 if (it->getPseudoClass().size())
628 printf("Pseudo class: " + it->getPseudoClass() + " ");
629 printf("\n");
630
631 // Attributes
632 for (std::list<CSSParser::Attribute>::const_iterator at = it->getAttributes().begin();
633 at != it->getAttributes().end(); at++) {
634 printf(" ");
635 printf(at->getName());
636 printf("\n");
637 }
638 }*/
639 CSSParser::Selector s = c.getStyleForElement("p", "", "title", "active", CSSParser::Selector::Context());
640 c.parseInSelector(s, "some_added_attribute: 20px;some_added_attribute2:15.6px", TAG_CSS_PRIORITY);
641 for (std::list<CSSParser::Attribute>::const_iterator at = s.getAttributes().begin();
642 at != s.getAttributes().end(); at++) {
643 printf( "%s\n", (at->getName() + ": " + at->getUnparsedValue()).c_str() );
644 }
645 printf("Parsing took %f sec\n", (float)(GetTime() - start).seconds());
646 printf("==== CSS TEST END ====\n");
647 }
648