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