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