]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssselector.c
b25ad3e7171f81cca18dc80a4f4b47bda080e73b
[~andy/gtk] / gtk / gtkcssselector.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcssselectorprivate.h"
21
22 #include <string.h>
23
24 #include "gtkcssprovider.h"
25 #include "gtkstylecontextprivate.h"
26
27 typedef struct _GtkCssSelectorClass GtkCssSelectorClass;
28
29 struct _GtkCssSelectorClass {
30   const char        *name;
31
32   void              (* print)   (const GtkCssSelector       *selector,
33                                  GString                    *string);
34   gboolean          (* match)   (const GtkCssSelector       *selector,
35                                  GtkStateFlags               state,
36                                  const GtkWidgetPath        *path,
37                                  guint                       id,
38                                  guint                       sibling);
39
40   guint         increase_id_specificity :1;
41   guint         increase_class_specificity :1;
42   guint         increase_element_specificity :1;
43 };
44
45 struct _GtkCssSelector
46 {
47   const GtkCssSelectorClass *class;       /* type of check this selector does */
48   gconstpointer              data;        /* data for matching:
49                                              - interned string for CLASS, NAME and ID
50                                              - GUINT_TO_POINTER() for PSEUDOCLASS_REGION/STATE */
51 };
52
53 static gboolean
54 gtk_css_selector_match (const GtkCssSelector *selector,
55                         GtkStateFlags         state,
56                         const GtkWidgetPath  *path,
57                         guint                 id,
58                         guint                 sibling)
59 {
60   if (selector == NULL)
61     return TRUE;
62
63   return selector->class->match (selector, state, path, id, sibling);
64 }
65
66 static const GtkCssSelector *
67 gtk_css_selector_previous (const GtkCssSelector *selector)
68 {
69   selector = selector + 1;
70
71   return selector->class ? selector : NULL;
72 }
73
74 /* ANY */
75
76 static void
77 gtk_css_selector_any_print (const GtkCssSelector *selector,
78                             GString              *string)
79 {
80   g_string_append_c (string, '*');
81 }
82
83 static gboolean
84 gtk_css_selector_any_match (const GtkCssSelector *selector,
85                             GtkStateFlags         state,
86                             const GtkWidgetPath  *path,
87                             guint                 id,
88                             guint                 sibling)
89 {
90   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
91 }
92
93 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ANY = {
94   "any",
95   gtk_css_selector_any_print,
96   gtk_css_selector_any_match,
97   FALSE, FALSE, FALSE
98 };
99
100 /* DESCENDANT */
101
102 static void
103 gtk_css_selector_descendant_print (const GtkCssSelector *selector,
104                                    GString              *string)
105 {
106   g_string_append_c (string, ' ');
107 }
108
109 static gboolean
110 gtk_css_selector_descendant_match (const GtkCssSelector *selector,
111                                    GtkStateFlags         state,
112                                    const GtkWidgetPath  *path,
113                                    guint                 id,
114                                    guint                 sibling)
115 {
116   while (id-- > 0)
117     {
118       if (gtk_css_selector_match (gtk_css_selector_previous (selector),
119                                   0,
120                                   path,
121                                   id,
122                                   gtk_widget_path_iter_get_sibling_index (path, id)))
123         return TRUE;
124     }
125
126   return FALSE;
127 }
128
129 static const GtkCssSelectorClass GTK_CSS_SELECTOR_DESCENDANT = {
130   "descendant",
131   gtk_css_selector_descendant_print,
132   gtk_css_selector_descendant_match,
133   FALSE, FALSE, FALSE
134 };
135
136 /* CHILD */
137
138 static void
139 gtk_css_selector_child_print (const GtkCssSelector *selector,
140                               GString              *string)
141 {
142   g_string_append (string, " > ");
143 }
144
145 static gboolean
146 gtk_css_selector_child_match (const GtkCssSelector *selector,
147                               GtkStateFlags         state,
148                               const GtkWidgetPath  *path,
149                               guint                 id,
150                               guint                 sibling)
151 {
152   if (id == 0)
153     return FALSE;
154
155   return gtk_css_selector_match (gtk_css_selector_previous (selector),
156                                  0,
157                                  path,
158                                  id - 1,
159                                  gtk_widget_path_iter_get_sibling_index (path, id - 1));
160 }
161
162 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CHILD = {
163   "child",
164   gtk_css_selector_child_print,
165   gtk_css_selector_child_match,
166   FALSE, FALSE, FALSE
167 };
168
169 /* NAME */
170
171 static void
172 gtk_css_selector_name_print (const GtkCssSelector *selector,
173                              GString              *string)
174 {
175   g_string_append (string, selector->data);
176 }
177
178 static gboolean
179 gtk_css_selector_name_match (const GtkCssSelector *selector,
180                              GtkStateFlags         state,
181                              const GtkWidgetPath  *path,
182                              guint                 id,
183                              guint                 sibling)
184 {
185   GType type = g_type_from_name (selector->data);
186
187   if (!g_type_is_a (gtk_widget_path_iter_get_object_type (path, id), type))
188     return FALSE;
189
190   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
191 }
192
193 static const GtkCssSelectorClass GTK_CSS_SELECTOR_NAME = {
194   "name",
195   gtk_css_selector_name_print,
196   gtk_css_selector_name_match,
197   FALSE, FALSE, TRUE
198 };
199
200 /* REGION */
201
202 static void
203 gtk_css_selector_region_print (const GtkCssSelector *selector,
204                                GString              *string)
205 {
206   g_string_append (string, selector->data);
207 }
208
209 static gboolean
210 gtk_css_selector_region_match (const GtkCssSelector *selector,
211                                GtkStateFlags         state,
212                                const GtkWidgetPath  *path,
213                                guint                 id,
214                                guint                 sibling)
215 {
216   const GtkCssSelector *previous;
217
218   if (!gtk_widget_path_iter_has_region (path, id, selector->data, NULL))
219     return FALSE;
220
221   previous = gtk_css_selector_previous (selector);
222   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
223       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
224     return TRUE;
225
226   return gtk_css_selector_match (previous, state, path, id, sibling);
227 }
228
229 static const GtkCssSelectorClass GTK_CSS_SELECTOR_REGION = {
230   "region",
231   gtk_css_selector_region_print,
232   gtk_css_selector_region_match,
233   FALSE, FALSE, TRUE
234 };
235
236 /* CLASS */
237
238 static void
239 gtk_css_selector_class_print (const GtkCssSelector *selector,
240                               GString              *string)
241 {
242   g_string_append_c (string, '.');
243   g_string_append (string, selector->data);
244 }
245
246 static gboolean
247 gtk_css_selector_class_match (const GtkCssSelector *selector,
248                               GtkStateFlags         state,
249                               const GtkWidgetPath  *path,
250                               guint                 id,
251                               guint                 sibling)
252 {
253   if (!gtk_widget_path_iter_has_class (path, id, selector->data))
254     return FALSE;
255
256   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
257 }
258
259 static const GtkCssSelectorClass GTK_CSS_SELECTOR_CLASS = {
260   "class",
261   gtk_css_selector_class_print,
262   gtk_css_selector_class_match,
263   FALSE, TRUE, FALSE
264 };
265
266 /* ID */
267
268 static void
269 gtk_css_selector_id_print (const GtkCssSelector *selector,
270                            GString              *string)
271 {
272   g_string_append_c (string, '#');
273   g_string_append (string, selector->data);
274 }
275
276 static gboolean
277 gtk_css_selector_id_match (const GtkCssSelector *selector,
278                            GtkStateFlags         state,
279                            const GtkWidgetPath  *path,
280                            guint                 id,
281                            guint                 sibling)
282 {
283   if (!gtk_widget_path_iter_has_name (path, id, selector->data))
284     return FALSE;
285
286   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
287 }
288
289 static const GtkCssSelectorClass GTK_CSS_SELECTOR_ID = {
290   "id",
291   gtk_css_selector_id_print,
292   gtk_css_selector_id_match,
293   TRUE, FALSE, FALSE
294 };
295
296 /* PSEUDOCLASS FOR STATE */
297
298 static void
299 gtk_css_selector_pseudoclass_state_print (const GtkCssSelector *selector,
300                                           GString              *string)
301 {
302   static const char * state_names[] = {
303     "active",
304     "hover",
305     "selected",
306     "insensitive",
307     "inconsistent",
308     "focus",
309     "backdrop"
310   };
311   guint i, state;
312
313   state = GPOINTER_TO_UINT (selector->data);
314   g_string_append_c (string, ':');
315
316   for (i = 0; i < G_N_ELEMENTS (state_names); i++)
317     {
318       if (state == (1 << i))
319         {
320           g_string_append (string, state_names[i]);
321           return;
322         }
323     }
324
325   g_assert_not_reached ();
326 }
327
328 static gboolean
329 gtk_css_selector_pseudoclass_state_match (const GtkCssSelector *selector,
330                                           GtkStateFlags         state,
331                                           const GtkWidgetPath  *path,
332                                           guint                 id,
333                                           guint                 sibling)
334 {
335   if (!(GPOINTER_TO_UINT (selector->data) & state))
336     return FALSE;
337
338   return gtk_css_selector_match (gtk_css_selector_previous (selector), state, path, id, sibling);
339 }
340
341 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_STATE = {
342   "pseudoclass-state",
343   gtk_css_selector_pseudoclass_state_print,
344   gtk_css_selector_pseudoclass_state_match,
345   FALSE, TRUE, FALSE
346 };
347
348 /* PSEUDOCLASS FOR REGION */
349
350 static void
351 gtk_css_selector_pseudoclass_region_print (const GtkCssSelector *selector,
352                                            GString              *string)
353 {
354   static const char * flag_names[] = {
355     "nth-child(even)",
356     "nth-child(odd)",
357     "first-child",
358     "last-child",
359     "only-child",
360     "sorted"
361   };
362   guint i, state;
363
364   state = GPOINTER_TO_UINT (selector->data);
365   g_string_append_c (string, ':');
366
367   for (i = 0; i < G_N_ELEMENTS (flag_names); i++)
368     {
369       if (state == (1 << i))
370         {
371           g_string_append (string, flag_names[i]);
372           return;
373         }
374     }
375
376   g_assert_not_reached ();
377 }
378
379 static gboolean
380 gtk_css_selector_pseudoclass_region_match_for_region (const GtkCssSelector *selector,
381                                                       GtkStateFlags         state,
382                                                       const GtkWidgetPath  *path,
383                                                       guint                 id,
384                                                       guint                 sibling)
385 {
386   GtkRegionFlags selector_flags, path_flags;
387   const GtkCssSelector *previous;
388   
389   selector_flags = GPOINTER_TO_UINT (selector->data);
390   selector = gtk_css_selector_previous (selector);
391
392   if (!gtk_widget_path_iter_has_region (path, id, selector->data, &path_flags))
393     return FALSE;
394
395   if ((selector_flags & path_flags) != selector_flags)
396     return FALSE;
397
398   previous = gtk_css_selector_previous (selector);
399   if (previous && previous->class == &GTK_CSS_SELECTOR_DESCENDANT &&
400       gtk_css_selector_match (gtk_css_selector_previous (previous), state, path, id, sibling))
401     return TRUE;
402
403   return gtk_css_selector_match (previous, state, path, id, sibling);
404 }
405
406 static gboolean
407 gtk_css_selector_pseudoclass_region_match (const GtkCssSelector *selector,
408                                            GtkStateFlags         state,
409                                            const GtkWidgetPath  *path,
410                                            guint                 id,
411                                            guint                 sibling)
412 {
413   GtkRegionFlags region;
414   const GtkWidgetPath *siblings;
415   guint n_siblings;
416   const GtkCssSelector *previous;
417
418   previous = gtk_css_selector_previous (selector);
419   if (previous && previous->class == &GTK_CSS_SELECTOR_REGION)
420     return gtk_css_selector_pseudoclass_region_match_for_region (selector, state, path, id, sibling);
421
422   siblings = gtk_widget_path_iter_get_siblings (path, id);
423   if (siblings == NULL)
424     return 0;
425
426   region = GPOINTER_TO_UINT (selector->data);
427   n_siblings = gtk_widget_path_length (siblings);
428
429   switch (region)
430     {
431     case GTK_REGION_EVEN:
432       if (!(sibling % 2))
433         return FALSE;
434       break;
435     case GTK_REGION_ODD:
436       if (sibling % 2)
437         return FALSE;
438       break;
439     case GTK_REGION_FIRST:
440       if (sibling != 0)
441         return FALSE;
442       break;
443     case GTK_REGION_LAST:
444       if (sibling + 1 != n_siblings)
445         return FALSE;
446       break;
447     case GTK_REGION_ONLY:
448       if (n_siblings != 1)
449         return FALSE;
450       break;
451     case GTK_REGION_SORTED:
452       return FALSE;
453     default:
454       g_assert_not_reached ();
455       return FALSE;
456     }
457
458   return gtk_css_selector_match (previous, state, path, id, sibling);
459 }
460
461 static const GtkCssSelectorClass GTK_CSS_SELECTOR_PSEUDOCLASS_REGION = {
462   "pseudoclass-region",
463   gtk_css_selector_pseudoclass_region_print,
464   gtk_css_selector_pseudoclass_region_match,
465   FALSE, TRUE, FALSE
466 };
467
468 /* API */
469
470 static guint
471 gtk_css_selector_size (const GtkCssSelector *selector)
472 {
473   guint size = 0;
474
475   while (selector)
476     {
477       selector = gtk_css_selector_previous (selector);
478       size++;
479     }
480
481   return size;
482 }
483
484 static GtkCssSelector *
485 gtk_css_selector_new (const GtkCssSelectorClass *class,
486                       GtkCssSelector            *selector,
487                       gconstpointer              data)
488 {
489   guint size;
490
491   size = gtk_css_selector_size (selector);
492   selector = g_realloc (selector, sizeof (GtkCssSelector) * (size + 1) + sizeof (gpointer));
493   if (size == 0)
494     selector[1].class = NULL;
495   else
496     memmove (selector + 1, selector, sizeof (GtkCssSelector) * size + sizeof (gpointer));
497
498   selector->class = class;
499   selector->data = data;
500
501   return selector;
502 }
503
504 static GtkCssSelector *
505 parse_selector_class (GtkCssParser *parser, GtkCssSelector *selector)
506 {
507   char *name;
508     
509   name = _gtk_css_parser_try_name (parser, FALSE);
510
511   if (name == NULL)
512     {
513       _gtk_css_parser_error (parser, "Expected a valid name for class");
514       if (selector)
515         _gtk_css_selector_free (selector);
516       return NULL;
517     }
518
519   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CLASS,
520                                    selector,
521                                    g_intern_string (name));
522
523   g_free (name);
524
525   return selector;
526 }
527
528 static GtkCssSelector *
529 parse_selector_id (GtkCssParser *parser, GtkCssSelector *selector)
530 {
531   char *name;
532     
533   name = _gtk_css_parser_try_name (parser, FALSE);
534
535   if (name == NULL)
536     {
537       _gtk_css_parser_error (parser, "Expected a valid name for id");
538       if (selector)
539         _gtk_css_selector_free (selector);
540       return NULL;
541     }
542
543   selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ID,
544                                    selector,
545                                    g_intern_string (name));
546
547   g_free (name);
548
549   return selector;
550 }
551
552 static GtkCssSelector *
553 parse_selector_pseudo_class (GtkCssParser   *parser,
554                              GtkCssSelector *selector)
555 {
556   struct {
557     const char *name;
558     GtkRegionFlags region_flag;
559     GtkStateFlags state_flag;
560   } pseudo_classes[] = {
561     { "first-child",  GTK_REGION_FIRST, 0 },
562     { "last-child",   GTK_REGION_LAST, 0 },
563     { "only-child",   GTK_REGION_ONLY, 0 },
564     { "sorted",       GTK_REGION_SORTED, 0 },
565     { "active",       0, GTK_STATE_FLAG_ACTIVE },
566     { "prelight",     0, GTK_STATE_FLAG_PRELIGHT },
567     { "hover",        0, GTK_STATE_FLAG_PRELIGHT },
568     { "selected",     0, GTK_STATE_FLAG_SELECTED },
569     { "insensitive",  0, GTK_STATE_FLAG_INSENSITIVE },
570     { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT },
571     { "focused",      0, GTK_STATE_FLAG_FOCUSED },
572     { "focus",        0, GTK_STATE_FLAG_FOCUSED },
573     { "backdrop",     0, GTK_STATE_FLAG_BACKDROP },
574     { NULL, }
575   }, nth_child_classes[] = {
576     { "first",        GTK_REGION_FIRST, 0 },
577     { "last",         GTK_REGION_LAST, 0 },
578     { "even",         GTK_REGION_EVEN, 0 },
579     { "odd",          GTK_REGION_ODD, 0 },
580     { NULL, }
581   }, *classes;
582   guint i;
583   char *name;
584   GError *error;
585
586   name = _gtk_css_parser_try_ident (parser, FALSE);
587   if (name == NULL)
588     {
589       _gtk_css_parser_error (parser, "Missing name of pseudo-class");
590       if (selector)
591         _gtk_css_selector_free (selector);
592       return NULL;
593     }
594
595   if (_gtk_css_parser_try (parser, "(", TRUE))
596     {
597       char *function = name;
598
599       name = _gtk_css_parser_try_ident (parser, TRUE);
600       if (!_gtk_css_parser_try (parser, ")", FALSE))
601         {
602           _gtk_css_parser_error (parser, "Missing closing bracket for pseudo-class");
603           if (selector)
604             _gtk_css_selector_free (selector);
605           return NULL;
606         }
607
608       if (g_ascii_strcasecmp (function, "nth-child") != 0)
609         {
610           error = g_error_new (GTK_CSS_PROVIDER_ERROR,
611                                GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
612                                "Unknown pseudo-class '%s(%s)'", function, name ? name : "");
613           _gtk_css_parser_take_error (parser, error);
614           g_free (function);
615           g_free (name);
616           if (selector)
617             _gtk_css_selector_free (selector);
618           return NULL;
619         }
620       
621       g_free (function);
622     
623       if (name == NULL)
624         {
625           error = g_error_new (GTK_CSS_PROVIDER_ERROR,
626                                GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
627                                "Unknown pseudo-class 'nth-child(%s)'", name);
628           _gtk_css_parser_take_error (parser, error);
629           if (selector)
630             _gtk_css_selector_free (selector);
631           return NULL;
632         }
633
634       classes = nth_child_classes;
635     }
636   else
637     classes = pseudo_classes;
638
639   for (i = 0; classes[i].name != NULL; i++)
640     {
641       if (g_ascii_strcasecmp (name, classes[i].name) == 0)
642         {
643           g_free (name);
644
645           if (classes[i].region_flag)
646             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_REGION,
647                                              selector,
648                                              GUINT_TO_POINTER (classes[i].region_flag));
649           else
650             selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_PSEUDOCLASS_STATE,
651                                              selector,
652                                              GUINT_TO_POINTER (classes[i].state_flag));
653
654           return selector;
655         }
656     }
657
658   if (classes == nth_child_classes)
659     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
660                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
661                          "Unknown pseudo-class 'nth-child(%s)'", name);
662   else
663     error = g_error_new (GTK_CSS_PROVIDER_ERROR,
664                          GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE,
665                          "Unknown pseudo-class '%s'", name);
666
667   _gtk_css_parser_take_error (parser, error);
668   g_free (name);
669   if (selector)
670     _gtk_css_selector_free (selector);
671
672   return NULL;
673 }
674
675 static GtkCssSelector *
676 try_parse_name (GtkCssParser   *parser,
677                 GtkCssSelector *selector)
678 {
679   char *name;
680
681   name = _gtk_css_parser_try_ident (parser, FALSE);
682   if (name)
683     {
684       selector = gtk_css_selector_new (_gtk_style_context_check_region_name (name)
685                                        ? &GTK_CSS_SELECTOR_REGION
686                                        : &GTK_CSS_SELECTOR_NAME,
687                                        selector,
688                                        g_intern_string (name));
689       g_free (name);
690     }
691   else if (_gtk_css_parser_try (parser, "*", FALSE))
692     selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_ANY, selector, NULL);
693   
694   return selector;
695 }
696
697 static GtkCssSelector *
698 parse_simple_selector (GtkCssParser   *parser,
699                        GtkCssSelector *selector)
700 {
701   guint size = gtk_css_selector_size (selector);
702   
703   selector = try_parse_name (parser, selector);
704
705   do {
706       if (_gtk_css_parser_try (parser, "#", FALSE))
707         selector = parse_selector_id (parser, selector);
708       else if (_gtk_css_parser_try (parser, ".", FALSE))
709         selector = parse_selector_class (parser, selector);
710       else if (_gtk_css_parser_try (parser, ":", FALSE))
711         selector = parse_selector_pseudo_class (parser, selector);
712       else if (gtk_css_selector_size (selector) == size)
713         {
714           _gtk_css_parser_error (parser, "Expected a valid selector");
715           if (selector)
716             _gtk_css_selector_free (selector);
717           return NULL;
718         }
719       else
720         break;
721     }
722   while (selector && !_gtk_css_parser_is_eof (parser));
723
724   _gtk_css_parser_skip_whitespace (parser);
725
726   return selector;
727 }
728
729 GtkCssSelector *
730 _gtk_css_selector_parse (GtkCssParser *parser)
731 {
732   GtkCssSelector *selector = NULL;
733
734   while ((selector = parse_simple_selector (parser, selector)) &&
735          !_gtk_css_parser_is_eof (parser) &&
736          !_gtk_css_parser_begins_with (parser, ',') &&
737          !_gtk_css_parser_begins_with (parser, '{'))
738     {
739       if (_gtk_css_parser_try (parser, ">", TRUE))
740         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_CHILD, selector, NULL);
741       else
742         selector = gtk_css_selector_new (&GTK_CSS_SELECTOR_DESCENDANT, selector, NULL);
743     }
744
745   return selector;
746 }
747
748 void
749 _gtk_css_selector_free (GtkCssSelector *selector)
750 {
751   g_return_if_fail (selector != NULL);
752
753   g_free (selector);
754 }
755
756 void
757 _gtk_css_selector_print (const GtkCssSelector *selector,
758                          GString *             str)
759 {
760   const GtkCssSelector *previous;
761
762   g_return_if_fail (selector != NULL);
763
764   previous = gtk_css_selector_previous (selector);
765   if (previous)
766     _gtk_css_selector_print (previous, str);
767
768   selector->class->print (selector, str);
769 }
770
771 char *
772 _gtk_css_selector_to_string (const GtkCssSelector *selector)
773 {
774   GString *string;
775
776   g_return_val_if_fail (selector != NULL, NULL);
777
778   string = g_string_new (NULL);
779
780   _gtk_css_selector_print (selector, string);
781
782   return g_string_free (string, FALSE);
783 }
784
785 /**
786  * _gtk_css_selector_matches:
787  * @selector: the selector
788  * @path: the path to check
789  * @state: The state to match
790  *
791  * Checks if the @selector matches the given @path. If @length is
792  * smaller than the number of elements in @path, it is assumed that
793  * only the first @length element of @path are valid and the rest
794  * does not exist. This is useful for doing parent matches for the
795  * 'inherit' keyword.
796  *
797  * Returns: %TRUE if the selector matches @path
798  **/
799 gboolean
800 _gtk_css_selector_matches (const GtkCssSelector      *selector,
801                            const GtkWidgetPath       *path,
802                            GtkStateFlags              state)
803 {
804   guint length;
805
806   g_return_val_if_fail (selector != NULL, FALSE);
807   g_return_val_if_fail (path != NULL, FALSE);
808
809   length = gtk_widget_path_length (path);
810   if (length == 0)
811     return FALSE;
812
813   return gtk_css_selector_match (selector,
814                                  state,
815                                  path,
816                                  length - 1,
817                                  gtk_widget_path_iter_get_sibling_index (path, length - 1));
818 }
819
820 /* Computes specificity according to CSS 2.1.
821  * The arguments must be initialized to 0 */
822 static void
823 _gtk_css_selector_get_specificity (const GtkCssSelector *selector,
824                                    guint                *ids,
825                                    guint                *classes,
826                                    guint                *elements)
827 {
828   for (; selector; selector = gtk_css_selector_previous (selector))
829     {
830       const GtkCssSelectorClass *klass = selector->class;
831
832       if (klass->increase_id_specificity)
833         (*ids)++;
834       if (klass->increase_class_specificity)
835         (*classes)++;
836       if (klass->increase_element_specificity)
837         (*elements)++;
838     }
839 }
840
841 int
842 _gtk_css_selector_compare (const GtkCssSelector *a,
843                            const GtkCssSelector *b)
844 {
845   guint a_ids = 0, a_classes = 0, a_elements = 0;
846   guint b_ids = 0, b_classes = 0, b_elements = 0;
847   int compare;
848
849   _gtk_css_selector_get_specificity (a, &a_ids, &a_classes, &a_elements);
850   _gtk_css_selector_get_specificity (b, &b_ids, &b_classes, &b_elements);
851
852   compare = a_ids - b_ids;
853   if (compare)
854     return compare;
855
856   compare = a_classes - b_classes;
857   if (compare)
858     return compare;
859
860   return a_elements - b_elements;
861 }
862
863 GtkStateFlags
864 _gtk_css_selector_get_state_flags (const GtkCssSelector *selector)
865 {
866   GtkStateFlags state = 0;
867
868   g_return_val_if_fail (selector != NULL, 0);
869
870   for (; selector && selector->class == &GTK_CSS_SELECTOR_NAME; selector = gtk_css_selector_previous (selector))
871     state |= GPOINTER_TO_UINT (selector->data);
872
873   return state;
874 }
875