1 /*
2  * css-selector: A CSS simple selector class
3  *
4  * Copyright 2012-2020 Stephan Haller <nomad@froevel.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  *
21  *
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include <libxfdashboard/css-selector.h>
29 
30 #include <glib/gi18n-lib.h>
31 
32 #include <libxfdashboard/compat.h>
33 #include <libxfdashboard/debug.h>
34 
35 
36 /* Define this class in GObject system */
37 struct _XfdashboardCssSelectorPrivate
38 {
39 	/* Properties related */
40 	gint							priority;
41 
42 	/* Instance related */
43 	XfdashboardCssSelectorRule		*rule;
44 };
45 
46 G_DEFINE_TYPE_WITH_PRIVATE(XfdashboardCssSelector,
47 				xfdashboard_css_selector,
48 				G_TYPE_OBJECT)
49 
50 /* Properties */
51 enum
52 {
53 	PROP_0,
54 
55 	PROP_PRIORITY,
56 
57 	PROP_LAST
58 };
59 
60 static GParamSpec* XfdashboardCssSelectorProperties[PROP_LAST]={ 0, };
61 
62 /* IMPLEMENTATION: Private variables and methods */
63 typedef enum /*< skip,prefix=XFDASHBOARD_CSS_SELECTOR_RULE_MODE >*/
64 {
65 	XFDASHBOARD_CSS_SELECTOR_RULE_MODE_NONE=0,
66 	XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT,
67 	XFDASHBOARD_CSS_SELECTOR_RULE_MODE_ANCESTOR
68 } XfdashboardCssSelectorRuleMode;
69 
70 struct _XfdashboardCssSelectorRule
71 {
72 	gchar							*type;
73 	gchar							*id;
74 	gchar							*classes;
75 	gchar							*pseudoClasses;
76 	XfdashboardCssSelectorRule		*parentRule;
77 	XfdashboardCssSelectorRuleMode	parentRuleMode;
78 
79 	gchar							*source;
80 	gint							priority;
81 	guint							line;
82 	guint							position;
83 
84 	guint							origLine;
85 	guint							origPosition;
86 };
87 
88 /* Create rule */
_xfdashboard_css_selector_rule_new(const gchar * inSource,gint inPriority,guint inLine,guint inPosition)89 static XfdashboardCssSelectorRule* _xfdashboard_css_selector_rule_new(const gchar *inSource,
90 																		gint inPriority,
91 																		guint inLine,
92 																		guint inPosition)
93 {
94 	XfdashboardCssSelectorRule		*rule;
95 
96 	rule=g_slice_new0(XfdashboardCssSelectorRule);
97 	rule->source=g_strdup(inSource);
98 	rule->priority=inPriority;
99 	rule->origLine=rule->line=inLine;
100 	rule->origPosition=rule->position=inPosition;
101 
102 	return(rule);
103 }
104 
105 /* Destroy selector */
_xfdashboard_css_selector_rule_free(XfdashboardCssSelectorRule * inRule)106 static void _xfdashboard_css_selector_rule_free(XfdashboardCssSelectorRule *inRule)
107 {
108 	g_return_if_fail(inRule);
109 
110 	/* Free allocated resources */
111 	if(inRule->type) g_free(inRule->type);
112 	if(inRule->id) g_free(inRule->id);
113 	if(inRule->classes) g_free(inRule->classes);
114 	if(inRule->pseudoClasses) g_free(inRule->pseudoClasses);
115 	if(inRule->source) g_free(inRule->source);
116 
117 	/* Destroy parent selector */
118 	if(inRule->parentRule) _xfdashboard_css_selector_rule_free(inRule->parentRule);
119 
120 	/* Free selector itself */
121 	g_slice_free(XfdashboardCssSelectorRule, inRule);
122 }
123 
124 /* Get string for selector */
_xfdashboard_css_selector_rule_to_string(XfdashboardCssSelectorRule * inRule)125 static gchar* _xfdashboard_css_selector_rule_to_string(XfdashboardCssSelectorRule *inRule)
126 {
127 	gchar							*parentSelector;
128 	gchar							*selector;
129 
130 	g_return_val_if_fail(inRule, NULL);
131 
132 	selector=NULL;
133 	parentSelector=NULL;
134 
135 	/* If a parent selector is available get a string representation of it first */
136 	if(inRule->parentRule)
137 	{
138 		gchar						*temp;
139 
140 		switch(inRule->parentRuleMode)
141 		{
142 			case XFDASHBOARD_CSS_SELECTOR_RULE_MODE_ANCESTOR:
143 			case XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT:
144 				temp=_xfdashboard_css_selector_rule_to_string(inRule->parentRule);
145 				if(!temp)
146 				{
147 					g_critical("Could not create string for parent css selector");
148 					return(NULL);
149 				}
150 				break;
151 
152 			default:
153 				g_critical("Invalid mode for parent rule in CSS selector");
154 				return(NULL);
155 		}
156 
157 		parentSelector=g_strdup_printf("%s%s ",
158 										temp,
159 										(inRule->parentRuleMode==XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT) ? " >" : "");
160 
161 		g_free(temp);
162 	}
163 
164 	/* Build string for selector */
165 	selector=g_strdup_printf("%s%s%s%s%s%s%s%s",
166 								(parentSelector) ? parentSelector : "",
167 								(inRule->type) ? inRule->type : "",
168 								(inRule->id) ? "#" : "",
169 								(inRule->id) ? inRule->id : "",
170 								(inRule->classes) ? "." : "",
171 								(inRule->classes) ? inRule->classes : "",
172 								(inRule->pseudoClasses) ? ":" : "",
173 								(inRule->pseudoClasses) ? inRule->pseudoClasses : "");
174 
175 	/* Release allocated resources not needed anymore */
176 	if(parentSelector) g_free(parentSelector);
177 
178 	/* Return newly created string for selector */
179 	return(selector);
180 }
181 
182 /* Check if haystack contains needle.
183  * The haystack is a string representing a list which entries is seperated
184  * by a seperator character. This function looks up the haystack if it
185  * contains an entry matching the needle and returns TRUE in this case.
186  * Otherwise FALSE is returned. A needle length of -1 signals that needle
187  * is a NULL-terminated string and length should be determine automatically.
188  */
_xfdashboard_css_selector_list_contains(const gchar * inNeedle,gint inNeedleLength,const gchar * inHaystack,gchar inSeperator)189 static gboolean _xfdashboard_css_selector_list_contains(const gchar *inNeedle,
190 														gint inNeedleLength,
191 														const gchar *inHaystack,
192 														gchar inSeperator)
193 {
194 	const gchar		*start;
195 
196 	g_return_val_if_fail(inNeedle && *inNeedle!=0, FALSE);
197 	g_return_val_if_fail(inNeedleLength>0 || inNeedleLength==-1, FALSE);
198 	g_return_val_if_fail(inHaystack && *inHaystack!=0, FALSE);
199 	g_return_val_if_fail(inSeperator, FALSE);
200 
201 	/* If given length of needle is negative it is a NULL-terminated string */
202 	if(inNeedleLength<0) inNeedleLength=strlen(inNeedle);
203 
204 	/* Lookup needle in haystack */
205 	for(start=inHaystack; start; start=strchr(start, inSeperator))
206 	{
207 		gint		length;
208 		gchar		*nextEntry;
209 
210 		/* Move to character after separator */
211 		if(start[0]==inSeperator) start++;
212 
213 		/* Find end of this haystack entry */
214 		nextEntry=strchr(start, inSeperator);
215 		if(!nextEntry) length=strlen(start);
216 			else length=nextEntry-start;
217 
218 		/* If enrty in haystack is not of same length as needle,
219 		 * then it is not a match
220 		 */
221 		if(length!=inNeedleLength) continue;
222 
223 		if(!strncmp(inNeedle, start, inNeedleLength)) return(TRUE);
224 	}
225 
226 	/* Needle was not found */
227 	return(FALSE);
228 }
229 
230 /* Check and score this selector against stylable node.
231  * A score below 0 means that they did not match.
232  */
_xfdashboard_css_selector_score_node(XfdashboardCssSelectorRule * inRule,XfdashboardStylable * inStylable)233 static gint _xfdashboard_css_selector_score_node(XfdashboardCssSelectorRule *inRule,
234 															XfdashboardStylable *inStylable)
235 {
236 	gint					score;
237 	gint					a, b, c;
238 	const gchar				*classes;
239 	const gchar				*pseudoClasses;
240 	const gchar				*id;
241 
242 	g_return_val_if_fail(inRule, -1);
243 	g_return_val_if_fail(XFDASHBOARD_IS_STYLABLE(inStylable), -1);
244 
245 	/* For information about how the scoring is done, see documentation
246 	 * "Cascading Style Sheets, level 1" of W3C, section "3.2 Cascading order"
247 	 * URL: http://www.w3.org/TR/2008/REC-CSS1-20080411/#cascading-order
248 	 *
249 	 * 1. Find all declarations that apply to the element/property in question.
250 	 *    Declarations apply if the selector matches the element in question.
251 	 *    If no declarations apply, the inherited value is used. If there is
252 	 *    no inherited value (this is the case for the 'HTML' element and
253 	 *    for properties that do not inherit), the initial value is used.
254 	 * 2. Sort the declarations by explicit weight: declarations marked
255 	 *    '!important' carry more weight than unmarked (normal) declarations.
256 	 * 3. Sort by origin: the author's style sheets override the reader's
257 	 *    style sheet which override the UA's default values. An imported
258 	 *    style sheet has the same origin as the style sheet from which it
259 	 *    is imported.
260 	 * 4. Sort by specificity of selector: more specific selectors will
261 	 *    override more general ones. To find the specificity, count the
262 	 *    number of ID attributes in the selector (a), the number of CLASS
263 	 *    attributes in the selector (b), and the number of tag names in
264 	 *    the selector (c). Concatenating the three numbers (in a number
265 	 *    system with a large base) gives the specificity.
266 	 *    Pseudo-elements and pseudo-classes are counted as normal elements
267 	 *    and classes, respectively.
268 	 * 5. Sort by order specified: if two rules have the same weight, the
269 	 *    latter specified wins. Rules in imported style sheets are considered
270 	 *    to be before any rules in the style sheet itself.
271 	 *
272 	 * NOTE: Keyword '!important' is not supported.
273 	 */
274 	a=b=c=0;
275 
276 	/* Get properties for given stylable */
277 	id=xfdashboard_stylable_get_name(XFDASHBOARD_STYLABLE(inStylable));
278 	classes=xfdashboard_stylable_get_classes(XFDASHBOARD_STYLABLE(inStylable));
279 	pseudoClasses=xfdashboard_stylable_get_pseudo_classes(XFDASHBOARD_STYLABLE(inStylable));
280 
281 	/* Check and score type of selectors but ignore NULL or universal selectors */
282 	if(inRule->type && inRule->type[0]!='*')
283 	{
284 		GType						ruleTypeID;
285 		GType						nodeTypeID;
286 
287 		/* Get type of this rule */
288 		ruleTypeID=g_type_from_name(inRule->type);
289 		if(!ruleTypeID) return(-1);
290 
291 		/* Get type of other rule to check against and score it */
292 		nodeTypeID=G_OBJECT_TYPE(inStylable);
293 		if(!nodeTypeID) return(-1);
294 
295 		/* Check if type of this rule matches type of other rule */
296 		if(!g_type_is_a(nodeTypeID, ruleTypeID)) return(-1);
297 
298 		/* Determine depth difference between both types
299 		 * which is the score of this test with a maximum of 99
300 		 */
301 		c=g_type_depth(ruleTypeID)-g_type_depth(nodeTypeID);
302 		c=MAX(ABS(c), 99);
303 	}
304 
305 	/* Check and score ID */
306 	if(inRule->id)
307 	{
308 		/* If node has no ID return immediately */
309 		if(!id || strcmp(inRule->id, id)) return(-1);
310 
311 		/* Score ID */
312 		a+=10;
313 	}
314 
315 	/* Check and score classes */
316 	if(inRule->classes)
317 	{
318 		gchar				*needle;
319 		gint				numberMatches;
320 
321 		/* If node has no pseudo class return immediately */
322 		if(!classes) return(-1);
323 
324 		/* Check that each class from the selector's rule appears in the
325 		 * list of classes from the node, i.e. the selector's rule class list
326 		 * is a subset of the node's class list
327 		 */
328 		numberMatches=0;
329 		for(needle=inRule->classes; needle; needle=strchr(needle, '.'))
330 		{
331 			gint			needleLength;
332 			gchar			*nextNeedle;
333 
334 			/* Move pointer of needle beyond class seperator '.' */
335 			if(needle[0]=='.') needle++;
336 
337 			/* Get length of needle */
338 			nextNeedle=strchr(needle, '.');
339 			if(nextNeedle) needleLength=nextNeedle-needle;
340 				else needleLength=strlen(needle);
341 
342 			/* If pseudo-class from the selector does not appear in the
343 			 * list of pseudo-classes from the node, then this is not a
344 			 * match
345 			 */
346 			if(!_xfdashboard_css_selector_list_contains(needle, needleLength, classes, '.')) return(-1);
347 			numberMatches++;
348 		}
349 
350 		/* Score matching class */
351 		b=b+(10*numberMatches);
352 	}
353 
354 	/* Check and score pseudo classes */
355 	if(inRule->pseudoClasses)
356 	{
357 		gchar				*needle;
358 		gint				numberMatches;
359 
360 		/* If node has no pseudo class return immediately */
361 		if(!pseudoClasses) return(-1);
362 
363 		/* Check that each pseudo-class from the selector appears in the
364 		 * pseudo-classes from the node, i.e. the selector pseudo-class list
365 		 * is a subset of the node's pseudo-class list
366 		 */
367 		numberMatches=0;
368 		for(needle=inRule->pseudoClasses; needle; needle=strchr(needle, ':'))
369 		{
370 			gint			needleLength;
371 			gchar			*nextNeedle;
372 
373 			/* Move pointer of needle beyond pseudo-class seperator ':' */
374 			if(needle[0]==':') needle++;
375 
376 			/* Get length of needle */
377 			nextNeedle=strchr(needle, ':');
378 			if(nextNeedle) needleLength=nextNeedle-needle;
379 				else needleLength=strlen(needle);
380 
381 			/* If pseudo-class from the selector does not appear in the
382 			 * list of pseudo-classes from the node, then this is not a
383 			 * match
384 			 */
385 			if(!_xfdashboard_css_selector_list_contains(needle, needleLength, pseudoClasses, ':')) return(-1);
386 			numberMatches++;
387 		}
388 
389 		/* Score matching pseudo-class */
390 		b=b+(10*numberMatches);
391 	}
392 
393 	/* Check and score parent */
394 	if(inRule->parentRule &&
395 		inRule->parentRuleMode==XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT)
396 	{
397 		gint					parentScore;
398 		XfdashboardStylable		*parent;
399 
400 		/* If node has no parent, no parent can match ;) so return immediately */
401 		parent=xfdashboard_stylable_get_parent(inStylable);
402 		if(!parent || !XFDASHBOARD_IS_STYLABLE(parent)) return(-1);
403 
404 		/* Check if there are matching parents. If not return immediately. */
405 		parentScore=_xfdashboard_css_selector_score_node(inRule->parentRule, parent);
406 		if(parentScore<0) return(-1);
407 
408 		/* Score matching parents */
409 		c+=parentScore;
410 	}
411 
412 	/* Check and score ancestor */
413 	if(inRule->parentRule &&
414 		inRule->parentRuleMode==XFDASHBOARD_CSS_SELECTOR_RULE_MODE_ANCESTOR)
415 	{
416 		gint					ancestorScore;
417 		XfdashboardStylable		*ancestor;
418 
419 		ancestor=inStylable;
420 
421 		/* If node has no parents, no ancestor can match so return immediately */
422 		do
423 		{
424 			ancestor=xfdashboard_stylable_get_parent(ancestor);
425 		}
426 		while(ancestor && !XFDASHBOARD_IS_STYLABLE(ancestor));
427 
428 		if(!ancestor || !XFDASHBOARD_IS_STYLABLE(ancestor)) return(-1);
429 
430 		/* Iterate through ancestors and check and score them */
431 		while(ancestor)
432 		{
433 			/* Get number of matches for ancestor and if at least one matches,
434 			 * stop search and score
435 			 */
436 			ancestorScore=_xfdashboard_css_selector_score_node(inRule->parentRule, ancestor);
437 			if(ancestorScore>=0)
438 			{
439 				c+=ancestorScore;
440 				break;
441 			}
442 
443 			/* Get next ancestor to check but skip actors not implementing
444 			 * the XfdashboardStylable interface
445 			 */
446 			do
447 			{
448 				ancestor=xfdashboard_stylable_get_parent(ancestor);
449 			}
450 			while(ancestor && !XFDASHBOARD_IS_STYLABLE(ancestor));
451 
452 			if(!ancestor || !XFDASHBOARD_IS_STYLABLE(ancestor)) return(-1);
453 		}
454 	}
455 
456 	/* Calculate final score */
457 	score=(a*10000)+(b*100)+c;
458 	return(score);
459 }
460 
461 /* Parse selector */
_xfdashboard_css_selector_parse_css_simple_selector(XfdashboardCssSelector * self,GScanner * inScanner,XfdashboardCssSelectorRule * ioRule)462 static GTokenType _xfdashboard_css_selector_parse_css_simple_selector(XfdashboardCssSelector *self,
463 																		GScanner *inScanner,
464 																		XfdashboardCssSelectorRule *ioRule)
465 {
466 	GTokenType		token;
467 
468 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), G_TOKEN_ERROR);
469 	g_return_val_if_fail(inScanner, G_TOKEN_ERROR);
470 	g_return_val_if_fail(ioRule, G_TOKEN_ERROR);
471 
472 	/* Parse type of selector. It is optional as '*' can be used as wildcard */
473 	token=g_scanner_peek_next_token(inScanner);
474 	switch((guint)token)
475 	{
476 		case '*':
477 			g_scanner_get_next_token(inScanner);
478 			ioRule->type=g_strdup("*");
479 
480 			/* Check if next token follows directly after this identifier.
481 			 * It is determined by checking if scanner needs to move more than
482 			 * one (the next) character. If there is a gap then either a new
483 			 * selector follows or it is a new typeless selector.
484 			 */
485 			token=g_scanner_peek_next_token(inScanner);
486 			if(inScanner->next_line==g_scanner_cur_line(inScanner) &&
487 				(inScanner->next_position-g_scanner_cur_position(inScanner))>1)
488 			{
489 				return(G_TOKEN_NONE);
490 			}
491 			break;
492 
493 		case G_TOKEN_IDENTIFIER:
494 			g_scanner_get_next_token(inScanner);
495 			ioRule->type=g_strdup(inScanner->value.v_identifier);
496 
497 			/* Check if next token follows directly after this identifier.
498 			 * It is determined by checking if scanner needs to move more than
499 			 * one (the next) character. If there is a gap then either a new
500 			 * selector follows or it is a new typeless selector.
501 			 */
502 			token=g_scanner_peek_next_token(inScanner);
503 			if(inScanner->next_line==g_scanner_cur_line(inScanner) &&
504 				(inScanner->next_position-g_scanner_cur_position(inScanner))>1)
505 			{
506 				return(G_TOKEN_NONE);
507 			}
508 			break;
509 
510 		default:
511 			break;
512 	}
513 
514 	/* Here we look for '#', '.' or ':' and return if we find anything else */
515 	token=g_scanner_peek_next_token(inScanner);
516 	while(token!=G_TOKEN_NONE)
517 	{
518 		switch((guint)token)
519 		{
520 			/* Parse ID */
521 			case '#':
522 				g_scanner_get_next_token(inScanner);
523 				token=g_scanner_get_next_token(inScanner);
524 				if(token!=G_TOKEN_IDENTIFIER)
525 				{
526 					g_scanner_unexp_token(inScanner,
527 											G_TOKEN_IDENTIFIER,
528 											NULL,
529 											NULL,
530 											NULL,
531 											"Invalid name identifier",
532 											TRUE);
533 					return(G_TOKEN_ERROR);
534 				}
535 
536 				/* Return immediately if ID was already set because it should be a new child but print a debug message */
537 				if(ioRule->id)
538 				{
539 					XFDASHBOARD_DEBUG(self, STYLE,
540 										"Unexpected new ID '%s' at rule %p for previous ID '%s' at line %d and position %d",
541 										inScanner->value.v_identifier,
542 										ioRule,
543 										ioRule->id,
544 										g_scanner_cur_line(inScanner),
545 										g_scanner_cur_position(inScanner));
546 					return(G_TOKEN_NONE);
547 				}
548 
549 				ioRule->id=g_strdup(inScanner->value.v_identifier);
550 				break;
551 
552 			/* Parse class */
553 			case '.':
554 				g_scanner_get_next_token(inScanner);
555 				token=g_scanner_get_next_token(inScanner);
556 				if(token!=G_TOKEN_IDENTIFIER)
557 				{
558 					g_scanner_unexp_token(inScanner,
559 											G_TOKEN_IDENTIFIER,
560 											NULL,
561 											NULL,
562 											NULL,
563 											"Invalid class identifier",
564 											TRUE);
565 					return(G_TOKEN_ERROR);
566 				}
567 
568 				if(ioRule->classes)
569 				{
570 					/* Remember old classes as it can only be freed afterwards */
571 					gchar		*oldClasses=ioRule->classes;
572 
573 					/* Create new list of classes */
574 					ioRule->classes=g_strconcat(ioRule->classes,
575 												".",
576 												inScanner->value.v_identifier,
577 												NULL);
578 
579 					/* Now free old classes */
580 					g_free(oldClasses);
581 				}
582 					else
583 					{
584 						ioRule->classes=g_strdup(inScanner->value.v_identifier);
585 					}
586 				break;
587 
588 			/* Parse pseudo-class */
589 			case ':':
590 				g_scanner_get_next_token(inScanner);
591 				token=g_scanner_get_next_token(inScanner);
592 				if(token!=G_TOKEN_IDENTIFIER)
593 				{
594 					g_scanner_unexp_token(inScanner,
595 											G_TOKEN_IDENTIFIER,
596 											NULL,
597 											NULL,
598 											NULL,
599 											"Invalid pseudo-class identifier",
600 											TRUE);
601 					return(G_TOKEN_ERROR);
602 				}
603 
604 				if(ioRule->pseudoClasses)
605 				{
606 					/* Remember old pseudo-classes as it can only be freed afterwards */
607 					gchar		*oldPseudoClasses=ioRule->pseudoClasses;
608 
609 					/* Create new list of pseudo-classes */
610 					ioRule->pseudoClasses=g_strconcat(ioRule->pseudoClasses,
611 														":",
612 														inScanner->value.v_identifier,
613 														NULL);
614 
615 					/* Now free old pseudo-classes */
616 					g_free(oldPseudoClasses);
617 				}
618 					else
619 					{
620 						ioRule->pseudoClasses=g_strdup(inScanner->value.v_identifier);
621 					}
622 				break;
623 
624 			default:
625 				return(G_TOKEN_NONE);
626 		}
627 
628 		/* Get next token */
629 		token=g_scanner_peek_next_token(inScanner);
630 	}
631 
632 	/* Successfully parsed */
633 	return(G_TOKEN_NONE);
634 }
635 
_xfdashboard_css_selector_parse_css_rule(XfdashboardCssSelector * self,GScanner * inScanner)636 static GTokenType _xfdashboard_css_selector_parse_css_rule(XfdashboardCssSelector *self,
637 															GScanner *inScanner)
638 {
639 	XfdashboardCssSelectorPrivate	*priv;
640 	GTokenType						token;
641 	XfdashboardCssSelectorRule		*rule, *parentRule;
642 
643 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), G_TOKEN_ERROR);
644 	g_return_val_if_fail(inScanner, G_TOKEN_ERROR);
645 
646 	priv=self->priv;
647 
648 	/* Parse comma-seperated selectors until a left curly bracket is found */
649 	parentRule=NULL;
650 	rule=NULL;
651 
652 	token=g_scanner_peek_next_token(inScanner);
653 	while(token!=G_TOKEN_EOF)
654 	{
655 		switch((guint)token)
656 		{
657 			case G_TOKEN_IDENTIFIER:
658 			case '*':
659 			case '#':
660 			case '.':
661 			case ':':
662 				/* Set last selector as parent if available */
663 				if(rule) parentRule=rule;
664 					else parentRule=NULL;
665 
666 				/* Create new selector */
667 				rule=_xfdashboard_css_selector_rule_new(inScanner->input_name,
668 														priv->priority,
669 														g_scanner_cur_line(inScanner),
670 														g_scanner_cur_position(inScanner));
671 				priv->rule=rule;
672 
673 				/* Check if there was a previous selector and if so, the new one
674 				 * should use the previous selector to match an ancestor
675 				 */
676 				if(parentRule)
677 				{
678 					rule->parentRule=parentRule;
679 					rule->parentRuleMode=XFDASHBOARD_CSS_SELECTOR_RULE_MODE_ANCESTOR;
680 				}
681 
682 				/* Parse selector */
683 				token=_xfdashboard_css_selector_parse_css_simple_selector(self, inScanner, rule);
684 				if(token!=G_TOKEN_NONE) return(token);
685 				break;
686 
687 			case '>':
688 				g_scanner_get_next_token(inScanner);
689 
690 				/* Set last selector as parent selector */
691 				if(!rule)
692 				{
693 					g_scanner_unexp_token(inScanner,
694 											G_TOKEN_IDENTIFIER,
695 											NULL,
696 											NULL,
697 											NULL,
698 											"No parent when parsing '>'",
699 											TRUE);
700 					return(G_TOKEN_ERROR);
701 				}
702 				parentRule=rule;
703 
704 				/* Create new selector */
705 				rule=_xfdashboard_css_selector_rule_new(inScanner->input_name,
706 														priv->priority,
707 														g_scanner_cur_line(inScanner),
708 														g_scanner_cur_position(inScanner));
709 				priv->rule=rule;
710 
711 				/* Link parent to the new selector as parent selector */
712 				rule->parentRule=parentRule;
713 				rule->parentRuleMode=XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT;
714 
715 
716 				/* Parse selector */
717 				token=_xfdashboard_css_selector_parse_css_simple_selector(self, inScanner, rule);
718 				if(token!=G_TOKEN_NONE) return(token);
719 				break;
720 
721 			default:
722 				/* Stop at first invalid character in stream and
723 				 * return with success result.
724 				 */
725 				return(G_TOKEN_NONE);
726 		}
727 
728 		/* Continue parsing with next token */
729 		token=g_scanner_peek_next_token(inScanner);
730 	}
731 
732 	/* Eat "eof" token */
733 	if(token==G_TOKEN_EOF) token=g_scanner_get_next_token(inScanner);
734 
735 	/* Successfully parsed */
736 	return(G_TOKEN_EOF);
737 }
738 
_xfdashboard_css_selector_parse(XfdashboardCssSelector * self,GScanner * ioScanner)739 static gboolean _xfdashboard_css_selector_parse(XfdashboardCssSelector *self, GScanner *ioScanner)
740 {
741 	GScannerConfig			*oldScannerConfig;
742 	GScannerConfig			*scannerConfig;
743 	gboolean				success;
744 	GTokenType				token;
745 
746 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), FALSE);
747 	g_return_val_if_fail(ioScanner, FALSE);
748 
749 	success=TRUE;
750 
751 	/* Set up scanner configuration for parsing css selectors:
752 	 * - Identifiers are allowed to contain '-' (minus sign) as non-first characters
753 	 * - Disallow scanning float values as we need '.' for identifiers
754 	 * - Set up single comment line not to include '#' as this character is need for identifiers
755 	 * - Disable parsing HEX values
756 	 * - Identifiers cannot be single quoted
757 	 * - Identifiers cannot be double quoted
758 	 */
759 	scannerConfig=(GScannerConfig*)g_memdup(ioScanner->config, sizeof(GScannerConfig));
760 	scannerConfig->cset_skip_characters=" \n\r\t";
761 	scannerConfig->cset_identifier_nth=G_CSET_a_2_z "-_0123456789" G_CSET_A_2_Z G_CSET_LATINS G_CSET_LATINC;
762 	scannerConfig->scan_float=FALSE;
763 	scannerConfig->cpair_comment_single="\1\n";
764 	scannerConfig->scan_hex=FALSE;
765 	scannerConfig->scan_string_sq=FALSE;
766 	scannerConfig->scan_string_dq=FALSE;
767 
768 	/* Set new scanner configuration but remember old one to restore it later */
769 	oldScannerConfig=ioScanner->config;
770 	ioScanner->config=scannerConfig;
771 
772 	/* Parse input stream */
773 	token=g_scanner_peek_next_token(ioScanner);
774 	if(token!=G_TOKEN_EOF)
775 	{
776 		token=_xfdashboard_css_selector_parse_css_rule(self, ioScanner);
777 		if(token==G_TOKEN_ERROR)
778 		{
779 			g_warning("Failed to parse css selector.");
780 			success=FALSE;
781 		}
782 	}
783 		else
784 		{
785 			g_warning("Failed to parse css selector because stream is empty.");
786 			success=FALSE;
787 		}
788 
789 	/* Restore old scanner configuration */
790 	ioScanner->config=oldScannerConfig;
791 
792 	/* Release allocated resources */
793 	g_free(scannerConfig);
794 
795 	/* Return success result */
796 	return(success);
797 }
798 
799 /* IMPLEMENTATION: GObject */
800 
801 /* Dispose this object */
_xfdashboard_css_selector_dispose(GObject * inObject)802 static void _xfdashboard_css_selector_dispose(GObject *inObject)
803 {
804 	XfdashboardCssSelector			*self=XFDASHBOARD_CSS_SELECTOR(inObject);
805 	XfdashboardCssSelectorPrivate	*priv=self->priv;
806 
807 	/* Release allocated resources */
808 	if(priv->rule)
809 	{
810 		_xfdashboard_css_selector_rule_free(priv->rule);
811 		priv->rule=NULL;
812 	}
813 
814 	/* Call parent's class dispose method */
815 	G_OBJECT_CLASS(xfdashboard_css_selector_parent_class)->dispose(inObject);
816 }
817 
818 /* Set/get properties */
_xfdashboard_css_selector_set_property(GObject * inObject,guint inPropID,const GValue * inValue,GParamSpec * inSpec)819 static void _xfdashboard_css_selector_set_property(GObject *inObject,
820 													guint inPropID,
821 													const GValue *inValue,
822 													GParamSpec *inSpec)
823 {
824 	XfdashboardCssSelector		*self=XFDASHBOARD_CSS_SELECTOR(inObject);
825 
826 	switch(inPropID)
827 	{
828 		case PROP_PRIORITY:
829 			self->priv->priority=g_value_get_int(inValue);
830 			break;
831 
832 		default:
833 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
834 			break;
835 	}
836 }
837 
_xfdashboard_css_selector_get_property(GObject * inObject,guint inPropID,GValue * outValue,GParamSpec * inSpec)838 static void _xfdashboard_css_selector_get_property(GObject *inObject,
839 													guint inPropID,
840 													GValue *outValue,
841 													GParamSpec *inSpec)
842 {
843 	XfdashboardCssSelector		*self=XFDASHBOARD_CSS_SELECTOR(inObject);
844 
845 	switch(inPropID)
846 	{
847 		case PROP_PRIORITY:
848 			g_value_set_int(outValue, self->priv->priority);
849 			break;
850 
851 		default:
852 			G_OBJECT_WARN_INVALID_PROPERTY_ID(inObject, inPropID, inSpec);
853 			break;
854 	}
855 }
856 
857 /* Class initialization
858  * Override functions in parent classes and define properties
859  * and signals
860  */
xfdashboard_css_selector_class_init(XfdashboardCssSelectorClass * klass)861 static void xfdashboard_css_selector_class_init(XfdashboardCssSelectorClass *klass)
862 {
863 	GObjectClass				*gobjectClass=G_OBJECT_CLASS(klass);
864 
865 	/* Override functions */
866 	gobjectClass->set_property=_xfdashboard_css_selector_set_property;
867 	gobjectClass->get_property=_xfdashboard_css_selector_get_property;
868 	gobjectClass->dispose=_xfdashboard_css_selector_dispose;
869 
870 	/* Define properties */
871 	XfdashboardCssSelectorProperties[PROP_PRIORITY]=
872 		g_param_spec_int("priority",
873 							"Priority",
874 							"The priority of this CSS selector",
875 							G_MININT, G_MAXINT,
876 							0,
877 							G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY);
878 
879 	g_object_class_install_properties(gobjectClass, PROP_LAST, XfdashboardCssSelectorProperties);
880 }
881 
882 /* Object initialization
883  * Create private structure and set up default values
884  */
xfdashboard_css_selector_init(XfdashboardCssSelector * self)885 static void xfdashboard_css_selector_init(XfdashboardCssSelector *self)
886 {
887 	XfdashboardCssSelectorPrivate	*priv;
888 
889 	priv=self->priv=xfdashboard_css_selector_get_instance_private(self);
890 
891 	/* Set up default values */
892 	priv->priority=0;
893 	priv->rule=NULL;
894 }
895 
896 /* IMPLEMENTATION: Public API */
897 
898 /* Create new instance of CSS selector by string */
xfdashboard_css_selector_new_from_string(const gchar * inSelector)899 XfdashboardCssSelector* xfdashboard_css_selector_new_from_string(const gchar *inSelector)
900 {
901 	return(xfdashboard_css_selector_new_from_string_with_priority(inSelector, G_MININT));
902 }
903 
xfdashboard_css_selector_new_from_string_with_priority(const gchar * inSelector,gint inPriority)904 XfdashboardCssSelector* xfdashboard_css_selector_new_from_string_with_priority(const gchar *inSelector, gint inPriority)
905 {
906 	GObject				*selector;
907 	GScanner			*scanner;
908 
909 	g_return_val_if_fail(inSelector || *inSelector, NULL);
910 
911 	/* Create selector instance */
912 	selector=g_object_new(XFDASHBOARD_TYPE_CSS_SELECTOR,
913 							"priority", inPriority,
914 							NULL);
915 	if(!selector)
916 	{
917 		g_warning("Could not create selector.");
918 		return(NULL);
919 	}
920 
921 	/* Create scanner for requested string */
922 	scanner=g_scanner_new(NULL);
923 	g_scanner_input_text(scanner, inSelector, strlen(inSelector));
924 
925 	/* Parse string with created scanner */
926 	if(!_xfdashboard_css_selector_parse(XFDASHBOARD_CSS_SELECTOR(selector), scanner))
927 	{
928 		g_object_unref(selector);
929 		selector=NULL;
930 	}
931 
932 	/* If scanner does not point to EOF after parsing, it is an error and selector
933 	 * needs to be destroyed.
934 	 */
935 	if(selector &&
936 		!g_scanner_eof(scanner))
937 	{
938 		/* It is not the end of string so print parser error message */
939 		g_scanner_unexp_token(scanner,
940 								G_TOKEN_EOF,
941 								NULL,
942 								NULL,
943 								NULL,
944 								"Parser did not reach end of stream",
945 								TRUE);
946 
947 		g_object_unref(selector);
948 		selector=NULL;
949 	}
950 
951 	/* Destroy allocated resources */
952 	g_scanner_destroy(scanner);
953 
954 	/* Return created selector which may be NULL in case of error */
955 	return(XFDASHBOARD_CSS_SELECTOR(selector));
956 }
957 
958 /* Create new instance of CSS selector with initialized scanner.
959  * Parsing stops at first invalid token for a selector. The callee is responsible
960  * to check if scanner points to a valid token in his context (e.g. CSS ruleset,
961  * a comma-separated list of CSS selectos) and to unref the returned selector
962  * if scanner points to an invalid token.
963  */
xfdashboard_css_selector_new_from_scanner(GScanner * ioScanner,XfdashboardCssSelectorParseFinishCallback inFinishCallback,gpointer inUserData)964 XfdashboardCssSelector* xfdashboard_css_selector_new_from_scanner(GScanner *ioScanner,
965 																	XfdashboardCssSelectorParseFinishCallback inFinishCallback,
966 																	gpointer inUserData)
967 {
968 	return(xfdashboard_css_selector_new_from_scanner_with_priority(ioScanner, G_MININT, inFinishCallback, inUserData));
969 }
970 
xfdashboard_css_selector_new_from_scanner_with_priority(GScanner * ioScanner,gint inPriority,XfdashboardCssSelectorParseFinishCallback inFinishCallback,gpointer inUserData)971 XfdashboardCssSelector* xfdashboard_css_selector_new_from_scanner_with_priority(GScanner *ioScanner,
972 																				gint inPriority,
973 																				XfdashboardCssSelectorParseFinishCallback inFinishCallback,
974 																				gpointer inUserData)
975 {
976 	GObject				*selector;
977 
978 	g_return_val_if_fail(ioScanner, NULL);
979 	g_return_val_if_fail(!g_scanner_eof(ioScanner), NULL);
980 
981 	/* Create selector instance */
982 	selector=g_object_new(XFDASHBOARD_TYPE_CSS_SELECTOR,
983 							"priority", inPriority,
984 							NULL);
985 	if(!selector)
986 	{
987 		g_warning("Could not create selector.");
988 		return(NULL);
989 	}
990 
991 	/* Parse selector from scanner provided  */
992 	if(!_xfdashboard_css_selector_parse(XFDASHBOARD_CSS_SELECTOR(selector), ioScanner))
993 	{
994 		g_object_unref(selector);
995 		return(NULL);
996 	}
997 
998 	/* If a callback is given to call after parsing finished so call it now
999 	 * to determine if scanner is still in good state. If it is in bad state
1000 	 * then return NULL.
1001 	 */
1002 	if(inFinishCallback)
1003 	{
1004 		gboolean		goodState;
1005 
1006 		goodState=(inFinishCallback)(XFDASHBOARD_CSS_SELECTOR(selector), ioScanner, g_scanner_peek_next_token(ioScanner), inUserData);
1007 		if(!goodState)
1008 		{
1009 			g_scanner_unexp_token(ioScanner,
1010 									G_TOKEN_ERROR,
1011 									NULL,
1012 									NULL,
1013 									NULL,
1014 									"Unexpected state of CSS scanner.",
1015 									TRUE);
1016 			g_object_unref(selector);
1017 			return(NULL);
1018 		}
1019 	}
1020 
1021 	/* Return created selector which may be NULL in case of error */
1022 	return(XFDASHBOARD_CSS_SELECTOR(selector));
1023 }
1024 
1025 /* Get string for selector.
1026  * Free string returned with g_free().
1027  */
xfdashboard_css_selector_to_string(XfdashboardCssSelector * self)1028 gchar* xfdashboard_css_selector_to_string(XfdashboardCssSelector *self)
1029 {
1030 	XfdashboardCssSelectorPrivate	*priv;
1031 	gchar							*selector;
1032 
1033 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), NULL);
1034 
1035 	priv=self->priv;
1036 	selector=NULL;
1037 
1038 	/* Get string for selector */
1039 	if(priv->rule)
1040 	{
1041 		selector=_xfdashboard_css_selector_rule_to_string(priv->rule);
1042 	}
1043 
1044 	/* Return newly created string for selector */
1045 	return(selector);
1046 }
1047 
1048 /* Check and score this selector against a stylable node.
1049  * A score below 0 means that they did not match.
1050  */
xfdashboard_css_selector_score(XfdashboardCssSelector * self,XfdashboardStylable * inStylable)1051 gint xfdashboard_css_selector_score(XfdashboardCssSelector *self, XfdashboardStylable *inStylable)
1052 {
1053 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), -1);
1054 	g_return_val_if_fail(XFDASHBOARD_IS_STYLABLE(inStylable), -1);
1055 
1056 	/* Check and score rules */
1057 	return(_xfdashboard_css_selector_score_node(self->priv->rule, inStylable));
1058 }
1059 
1060 /* Adjust source line and position of this selector to an offset */
xfdashboard_css_selector_adjust_to_offset(XfdashboardCssSelector * self,gint inLine,gint inPosition)1061 void xfdashboard_css_selector_adjust_to_offset(XfdashboardCssSelector *self, gint inLine, gint inPosition)
1062 {
1063 	XfdashboardCssSelectorPrivate	*priv;
1064 	gint							newLine;
1065 	gint							newPosition;
1066 
1067 	g_return_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self));
1068 
1069 	priv=self->priv;
1070 
1071 	/* Adjust to offset */
1072 	if(priv->rule)
1073 	{
1074 		newLine=inLine+priv->rule->origLine;
1075 		priv->rule->line=MAX(0, newLine);
1076 
1077 		newPosition=inPosition+priv->rule->origPosition;
1078 		priv->rule->position=MAX(0, newPosition);
1079 	}
1080 }
1081 
1082 /* Get rule parsed */
xfdashboard_css_selector_get_rule(XfdashboardCssSelector * self)1083 XfdashboardCssSelectorRule* xfdashboard_css_selector_get_rule(XfdashboardCssSelector *self)
1084 {
1085 	g_return_val_if_fail(XFDASHBOARD_IS_CSS_SELECTOR(self), NULL);
1086 
1087 	return(self->priv->rule);
1088 }
1089 
1090 /* Get values from rule */
xfdashboard_css_selector_rule_get_type(XfdashboardCssSelectorRule * inRule)1091 const gchar* xfdashboard_css_selector_rule_get_type(XfdashboardCssSelectorRule *inRule)
1092 {
1093 	g_return_val_if_fail(inRule, NULL);
1094 
1095 	return(inRule->type);
1096 }
1097 
xfdashboard_css_selector_rule_get_id(XfdashboardCssSelectorRule * inRule)1098 const gchar* xfdashboard_css_selector_rule_get_id(XfdashboardCssSelectorRule *inRule)
1099 {
1100 	g_return_val_if_fail(inRule, NULL);
1101 
1102 	return(inRule->id);
1103 }
1104 
xfdashboard_css_selector_rule_get_classes(XfdashboardCssSelectorRule * inRule)1105 const gchar* xfdashboard_css_selector_rule_get_classes(XfdashboardCssSelectorRule *inRule)
1106 {
1107 	g_return_val_if_fail(inRule, NULL);
1108 
1109 	return(inRule->classes);
1110 }
1111 
xfdashboard_css_selector_rule_get_pseudo_classes(XfdashboardCssSelectorRule * inRule)1112 const gchar* xfdashboard_css_selector_rule_get_pseudo_classes(XfdashboardCssSelectorRule *inRule)
1113 {
1114 	g_return_val_if_fail(inRule, NULL);
1115 
1116 	return(inRule->pseudoClasses);
1117 }
1118 
xfdashboard_css_selector_rule_get_parent(XfdashboardCssSelectorRule * inRule)1119 XfdashboardCssSelectorRule* xfdashboard_css_selector_rule_get_parent(XfdashboardCssSelectorRule *inRule)
1120 {
1121 	g_return_val_if_fail(inRule, NULL);
1122 
1123 	if(inRule->parentRuleMode!=XFDASHBOARD_CSS_SELECTOR_RULE_MODE_PARENT) return(NULL);
1124 	return(inRule->parentRule);
1125 }
1126 
xfdashboard_css_selector_rule_get_ancestor(XfdashboardCssSelectorRule * inRule)1127 XfdashboardCssSelectorRule* xfdashboard_css_selector_rule_get_ancestor(XfdashboardCssSelectorRule *inRule)
1128 {
1129 	g_return_val_if_fail(inRule, NULL);
1130 
1131 	if(inRule->parentRuleMode!=XFDASHBOARD_CSS_SELECTOR_RULE_MODE_ANCESTOR) return(NULL);
1132 	return(inRule->parentRule);
1133 }
1134 
xfdashboard_css_selector_rule_get_source(XfdashboardCssSelectorRule * inRule)1135 const gchar* xfdashboard_css_selector_rule_get_source(XfdashboardCssSelectorRule *inRule)
1136 {
1137 	g_return_val_if_fail(inRule, NULL);
1138 
1139 	return(inRule->source);
1140 }
1141 
xfdashboard_css_selector_rule_get_priority(XfdashboardCssSelectorRule * inRule)1142 gint xfdashboard_css_selector_rule_get_priority(XfdashboardCssSelectorRule *inRule)
1143 {
1144 	g_return_val_if_fail(inRule, -1);
1145 
1146 	return(inRule->priority);
1147 }
1148 
xfdashboard_css_selector_rule_get_line(XfdashboardCssSelectorRule * inRule)1149 guint xfdashboard_css_selector_rule_get_line(XfdashboardCssSelectorRule *inRule)
1150 {
1151 	g_return_val_if_fail(inRule, -1);
1152 
1153 	return(inRule->line);
1154 }
1155 
xfdashboard_css_selector_rule_get_position(XfdashboardCssSelectorRule * inRule)1156 guint xfdashboard_css_selector_rule_get_position(XfdashboardCssSelectorRule *inRule)
1157 {
1158 	g_return_val_if_fail(inRule, -1);
1159 
1160 	return(inRule->position);
1161 }
1162