habaki 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/ext/katana/extconf.rb +20 -0
  4. data/ext/katana/rb_katana.c +280 -0
  5. data/ext/katana/rb_katana.h +102 -0
  6. data/ext/katana/rb_katana_array.c +144 -0
  7. data/ext/katana/rb_katana_declaration.c +389 -0
  8. data/ext/katana/rb_katana_rule.c +461 -0
  9. data/ext/katana/rb_katana_selector.c +559 -0
  10. data/ext/katana/src/foundation.c +237 -0
  11. data/ext/katana/src/foundation.h +120 -0
  12. data/ext/katana/src/katana.h +590 -0
  13. data/ext/katana/src/katana.lex.c +4104 -0
  14. data/ext/katana/src/katana.lex.h +592 -0
  15. data/ext/katana/src/katana.tab.c +4422 -0
  16. data/ext/katana/src/katana.tab.h +262 -0
  17. data/ext/katana/src/parser.c +1563 -0
  18. data/ext/katana/src/parser.h +237 -0
  19. data/ext/katana/src/selector.c +659 -0
  20. data/ext/katana/src/selector.h +54 -0
  21. data/ext/katana/src/tokenizer.c +300 -0
  22. data/ext/katana/src/tokenizer.h +41 -0
  23. data/lib/habaki/charset_rule.rb +25 -0
  24. data/lib/habaki/declaration.rb +53 -0
  25. data/lib/habaki/declarations.rb +346 -0
  26. data/lib/habaki/error.rb +43 -0
  27. data/lib/habaki/font_face_rule.rb +24 -0
  28. data/lib/habaki/formal_syntax.rb +464 -0
  29. data/lib/habaki/formatter.rb +99 -0
  30. data/lib/habaki/import_rule.rb +34 -0
  31. data/lib/habaki/media_rule.rb +173 -0
  32. data/lib/habaki/namespace_rule.rb +31 -0
  33. data/lib/habaki/node.rb +52 -0
  34. data/lib/habaki/page_rule.rb +24 -0
  35. data/lib/habaki/qualified_name.rb +29 -0
  36. data/lib/habaki/rule.rb +48 -0
  37. data/lib/habaki/rules.rb +225 -0
  38. data/lib/habaki/selector.rb +98 -0
  39. data/lib/habaki/selectors.rb +49 -0
  40. data/lib/habaki/style_rule.rb +35 -0
  41. data/lib/habaki/stylesheet.rb +158 -0
  42. data/lib/habaki/sub_selector.rb +234 -0
  43. data/lib/habaki/sub_selectors.rb +42 -0
  44. data/lib/habaki/supports_rule.rb +65 -0
  45. data/lib/habaki/value.rb +321 -0
  46. data/lib/habaki/values.rb +86 -0
  47. data/lib/habaki/visitor/element.rb +50 -0
  48. data/lib/habaki/visitor/media.rb +22 -0
  49. data/lib/habaki/visitor/nokogiri_element.rb +56 -0
  50. data/lib/habaki.rb +39 -0
  51. metadata +190 -0
@@ -0,0 +1,659 @@
1
+ /**
2
+ * Copyright (c) 2015 QFish <im@qfi.sh>
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ * THE SOFTWARE.
21
+ */
22
+
23
+ #include "selector.h"
24
+ #include <string.h>
25
+ #include <assert.h>
26
+ #include <strings.h>
27
+
28
+ #undef assert
29
+ #define assert(x)
30
+
31
+ // Refs:
32
+ // http://www.w3.org/TR/css3-selectors/
33
+ //
34
+
35
+ static KatanaPseudoType name_to_pseudo_type(const char* name, bool hasArguments);
36
+
37
+ bool katana_selector_crosses_tree_scopes(const KatanaSelector* selector)
38
+ {
39
+ // TODO: To be supported
40
+ return false;
41
+ }
42
+
43
+ // bool katana_is_attribute_selector(const KatanaSelector* selector)
44
+ // {
45
+ // return selector->match == KatanaSelectorMatchAttributeExact
46
+ // || selector->match == KatanaSelectorMatchAttributeSet
47
+ // || selector->match == KatanaSelectorMatchAttributeList
48
+ // || selector->match == KatanaSelectorMatchAttributeHyphen
49
+ // || selector->match == KatanaSelectorMatchAttributeContain
50
+ // || selector->match == KatanaSelectorMatchAttributeBegin
51
+ // || selector->match == KatanaSelectorMatchAttributeEnd;
52
+ // }
53
+
54
+ KatanaPseudoType katana_parse_pseudo_type(const char* name, bool hasArguments)
55
+ {
56
+ KatanaPseudoType pseudoType = name_to_pseudo_type(name, hasArguments);
57
+ if (pseudoType != KatanaPseudoUnknown)
58
+ return pseudoType;
59
+
60
+ if (katana_string_has_prefix(name, "-webkit-"))
61
+ return KatanaPseudoWebKitCustomElement;
62
+
63
+ return KatanaPseudoUnknown;
64
+ }
65
+
66
+ void katana_selector_extract_pseudo_type(KatanaSelector* selector)
67
+ {
68
+ if (selector->pseudo == KatanaPseudoNotParsed)
69
+ selector->pseudo = KatanaPseudoUnknown;
70
+
71
+ if (selector->match != KatanaSelectorMatchPseudoClass && selector->match != KatanaSelectorMatchPseudoElement && selector->match != KatanaSelectorMatchPagePseudoClass)
72
+ return;
73
+ bool hasArguments = (NULL != selector->data->argument) || (NULL != selector->data->selectors);
74
+ selector->pseudo = katana_parse_pseudo_type(selector->data->value, hasArguments);
75
+
76
+ bool element = false; // pseudo-element
77
+ bool compat = false; // single colon compatbility mode
78
+ bool isPagePseudoClass = false; // Page pseudo-class
79
+
80
+ switch (selector->pseudo) {
81
+ case KatanaPseudoAfter:
82
+ case KatanaPseudoBefore:
83
+ case KatanaPseudoFirstLetter:
84
+ case KatanaPseudoFirstLine:
85
+ compat = true;
86
+ case KatanaPseudoBackdrop:
87
+ case KatanaPseudoCue:
88
+ case KatanaPseudoResizer:
89
+ case KatanaPseudoScrollbar:
90
+ case KatanaPseudoScrollbarCorner:
91
+ case KatanaPseudoScrollbarButton:
92
+ case KatanaPseudoScrollbarThumb:
93
+ case KatanaPseudoScrollbarTrack:
94
+ case KatanaPseudoScrollbarTrackPiece:
95
+ case KatanaPseudoSelection:
96
+ case KatanaPseudoWebKitCustomElement:
97
+ case KatanaPseudoContent:
98
+ case KatanaPseudoShadow:
99
+ element = true;
100
+ break;
101
+ case KatanaPseudoUnknown:
102
+ case KatanaPseudoEmpty:
103
+ case KatanaPseudoFirstChild:
104
+ case KatanaPseudoFirstOfType:
105
+ case KatanaPseudoLastChild:
106
+ case KatanaPseudoLastOfType:
107
+ case KatanaPseudoOnlyChild:
108
+ case KatanaPseudoOnlyOfType:
109
+ case KatanaPseudoNthChild:
110
+ case KatanaPseudoNthOfType:
111
+ case KatanaPseudoNthLastChild:
112
+ case KatanaPseudoNthLastOfType:
113
+ case KatanaPseudoLink:
114
+ case KatanaPseudoVisited:
115
+ case KatanaPseudoAny:
116
+ case KatanaPseudoAnyLink:
117
+ case KatanaPseudoAutofill:
118
+ case KatanaPseudoHover:
119
+ case KatanaPseudoDrag:
120
+ case KatanaPseudoFocus:
121
+ case KatanaPseudoActive:
122
+ case KatanaPseudoChecked:
123
+ case KatanaPseudoEnabled:
124
+ case KatanaPseudoFullPageMedia:
125
+ case KatanaPseudoDefault:
126
+ case KatanaPseudoDisabled:
127
+ case KatanaPseudoOptional:
128
+ case KatanaPseudoRequired:
129
+ case KatanaPseudoReadOnly:
130
+ case KatanaPseudoReadWrite:
131
+ case KatanaPseudoScope:
132
+ case KatanaPseudoValid:
133
+ case KatanaPseudoInvalid:
134
+ case KatanaPseudoIndeterminate:
135
+ case KatanaPseudoTarget:
136
+ case KatanaPseudoLang:
137
+ case KatanaPseudoNot:
138
+ case KatanaPseudoRoot:
139
+ case KatanaPseudoWindowInactive:
140
+ case KatanaPseudoCornerPresent:
141
+ case KatanaPseudoDecrement:
142
+ case KatanaPseudoIncrement:
143
+ case KatanaPseudoHorizontal:
144
+ case KatanaPseudoVertical:
145
+ case KatanaPseudoStart:
146
+ case KatanaPseudoEnd:
147
+ case KatanaPseudoDoubleButton:
148
+ case KatanaPseudoSingleButton:
149
+ case KatanaPseudoNoButton:
150
+ case KatanaPseudoNotParsed:
151
+ case KatanaPseudoFullScreen:
152
+ case KatanaPseudoFullScreenDocument:
153
+ case KatanaPseudoFullScreenAncestor:
154
+ case KatanaPseudoInRange:
155
+ case KatanaPseudoOutOfRange:
156
+ case KatanaPseudoFutureCue:
157
+ case KatanaPseudoPastCue:
158
+ case KatanaPseudoHost:
159
+ case KatanaPseudoHostContext:
160
+ case KatanaPseudoUnresolved:
161
+ case KatanaPseudoSpatialNavigationFocus:
162
+ case KatanaPseudoListBox:
163
+ break;
164
+ case KatanaPseudoFirstPage:
165
+ case KatanaPseudoLeftPage:
166
+ case KatanaPseudoRightPage:
167
+ isPagePseudoClass = true;
168
+ break;
169
+ }
170
+
171
+ bool matchPagePseudoClass = (selector->match == KatanaSelectorMatchPagePseudoClass);
172
+ if (matchPagePseudoClass != isPagePseudoClass)
173
+ selector->pseudo = KatanaPseudoUnknown;
174
+ else if (selector->match == KatanaSelectorMatchPseudoClass && element) {
175
+ if (!compat)
176
+ selector->pseudo = KatanaPseudoUnknown;
177
+ else
178
+ selector->match = KatanaSelectorMatchPseudoElement;
179
+ } else if (selector->match == KatanaSelectorMatchPseudoElement && !element)
180
+ selector->pseudo = KatanaPseudoUnknown;
181
+ }
182
+
183
+ bool katana_selector_matches_pseudo_element(KatanaSelector* selector)
184
+ {
185
+ if (selector->pseudo == KatanaPseudoUnknown)
186
+ katana_selector_extract_pseudo_type(selector);
187
+ return selector->match == KatanaSelectorMatchPseudoElement;
188
+ }
189
+
190
+ bool katana_selector_is_custom_pseudo_element(KatanaSelector* selector)
191
+ {
192
+ return selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoWebKitCustomElement;
193
+ }
194
+
195
+ bool katana_selector_is_direct_adjacent(KatanaSelector* selector)
196
+ {
197
+ return selector->relation == KatanaSelectorRelationDirectAdjacent || selector->relation == KatanaSelectorRelationIndirectAdjacent;
198
+ }
199
+
200
+ bool katana_selector_is_adjacent(KatanaSelector* selector)
201
+ {
202
+ return selector->relation == KatanaSelectorRelationDirectAdjacent;
203
+ }
204
+
205
+ bool katana_selector_is_shadow(KatanaSelector* selector)
206
+ {
207
+ return selector->relation == KatanaSelectorRelationShadowPseudo || selector->relation == KatanaSelectorRelationShadowDeep;
208
+ }
209
+
210
+ bool katana_selector_is_sibling(KatanaSelector* selector)
211
+ {
212
+ katana_selector_extract_pseudo_type(selector);
213
+
214
+ KatanaPseudoType type = selector->pseudo;
215
+ return selector->relation == KatanaSelectorRelationDirectAdjacent
216
+ || selector->relation == KatanaSelectorRelationIndirectAdjacent
217
+ || type == KatanaPseudoEmpty
218
+ || type == KatanaPseudoFirstChild
219
+ || type == KatanaPseudoFirstOfType
220
+ || type == KatanaPseudoLastChild
221
+ || type == KatanaPseudoLastOfType
222
+ || type == KatanaPseudoOnlyChild
223
+ || type == KatanaPseudoOnlyOfType
224
+ || type == KatanaPseudoNthChild
225
+ || type == KatanaPseudoNthOfType
226
+ || type == KatanaPseudoNthLastChild
227
+ || type == KatanaPseudoNthLastOfType;
228
+ }
229
+
230
+ bool katana_selector_is_attribute(const KatanaSelector* selector)
231
+ {
232
+ return selector->match >= KatanaSelectorMatchFirstAttribute;
233
+ }
234
+
235
+ bool katana_selector_is_content_pseudo_element(KatanaSelector* selector)
236
+ {
237
+ katana_selector_extract_pseudo_type(selector);
238
+ return selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoContent;
239
+ }
240
+
241
+ bool katana_selector_is_shadow_pseudo_element(KatanaSelector* selector)
242
+ {
243
+ return selector->match == KatanaSelectorMatchPseudoElement
244
+ && selector->pseudo == KatanaPseudoShadow;
245
+ }
246
+
247
+ bool katana_selector_is_host_pseudo_class(KatanaSelector* selector)
248
+ {
249
+ return selector->match == KatanaSelectorMatchPseudoClass && (selector->pseudo == KatanaPseudoHost || selector->pseudo == KatanaPseudoHostContext);
250
+ }
251
+
252
+ bool katana_selector_is_tree_boundary_crossing(KatanaSelector* selector)
253
+ {
254
+ katana_selector_extract_pseudo_type(selector);
255
+ return selector->match == KatanaSelectorMatchPseudoClass && (selector->pseudo == KatanaPseudoHost || selector->pseudo == KatanaPseudoHostContext);
256
+ }
257
+
258
+ bool katana_selector_is_insertion_point_crossing(KatanaSelector* selector)
259
+ {
260
+ katana_selector_extract_pseudo_type(selector);
261
+ return (selector->match == KatanaSelectorMatchPseudoClass && selector->pseudo == KatanaPseudoHostContext)
262
+ || (selector->match == KatanaSelectorMatchPseudoElement && selector->pseudo == KatanaPseudoContent);
263
+ }
264
+
265
+ KatanaParserString* katana_build_relation_selector_string(KatanaParser* parser, const char* relation, KatanaParserString* string, KatanaParserString* next, KatanaSelector* tagHistory)
266
+ {
267
+ if ( NULL != relation ) {
268
+ katana_string_prepend_characters(parser, relation, string);
269
+ }
270
+
271
+ if ( NULL != next ) {
272
+ katana_string_append_string(parser, next, string);
273
+ }
274
+
275
+ KatanaParserString * result = katana_selector_to_string(parser, tagHistory, (KatanaParserString*)string);
276
+ katana_parser_deallocate(parser, (void*) string->data);
277
+ katana_parser_deallocate(parser, (void*) string);
278
+ return result;
279
+ }
280
+
281
+ KatanaParserString* katana_selector_to_string(KatanaParser* parser, KatanaSelector* selector, KatanaParserString* next)
282
+ {
283
+ KatanaParserString* string = katana_parser_allocate(parser, sizeof(KatanaParserString));
284
+ katana_string_init(parser, string);
285
+
286
+ bool tag_is_implicit = true;
287
+
288
+ if (selector->match == KatanaSelectorMatchTag && tag_is_implicit)
289
+ {
290
+ if ( NULL == selector->tag->prefix )
291
+ katana_string_append_characters(parser, selector->tag->local, string);
292
+ else {
293
+ katana_string_append_characters(parser, selector->tag->prefix, string);
294
+ katana_string_append_characters(parser, "|", string);
295
+ katana_string_append_characters(parser, selector->tag->local, string);
296
+ }
297
+ }
298
+
299
+ const KatanaSelector* cs = selector;
300
+
301
+ while (true) {
302
+ if (cs->match == KatanaSelectorMatchId) {
303
+ katana_string_append_characters(parser, "#", string);
304
+ katana_string_append_characters(parser, cs->data->value, string);
305
+ } else if (cs->match == KatanaSelectorMatchClass) {
306
+ katana_string_append_characters(parser, ".", string);
307
+ katana_string_append_characters(parser, cs->data->value, string);
308
+ } else if (cs->match == KatanaSelectorMatchPseudoClass || cs->match == KatanaSelectorMatchPagePseudoClass) {
309
+ katana_string_append_characters(parser, ":", string);
310
+ katana_string_append_characters(parser, cs->data->value, string);
311
+
312
+ switch (cs->pseudo) {
313
+ case KatanaPseudoAny:
314
+ case KatanaPseudoNot:
315
+ case KatanaPseudoHost:
316
+ case KatanaPseudoHostContext: {
317
+ if ( cs->data->selectors ) {
318
+ KatanaArray* sels = cs->data->selectors;
319
+ for (size_t i=0; i<sels->length; i++) {
320
+ KatanaParserString* str = katana_selector_to_string(parser, sels->data[i], NULL);
321
+ katana_string_append_string(parser, str, string);
322
+ katana_parser_deallocate(parser, (void*) str->data);
323
+ katana_parser_deallocate(parser, (void*) str);
324
+ if ( i != sels->length -1 ) {
325
+ katana_string_append_characters(parser, ", ", string);
326
+ }
327
+ }
328
+ katana_string_append_characters(parser, ")", string);
329
+ }
330
+ }
331
+ break;
332
+ case KatanaPseudoLang:
333
+ case KatanaPseudoNthChild:
334
+ case KatanaPseudoNthLastChild:
335
+ case KatanaPseudoNthOfType:
336
+ case KatanaPseudoNthLastOfType: {
337
+ katana_string_append_characters(parser, cs->data->argument, string);
338
+ katana_string_append_characters(parser, ")", string);
339
+ }
340
+ break;
341
+ default:
342
+ break;
343
+ }
344
+ } else if (cs->match == KatanaSelectorMatchPseudoElement) {
345
+ katana_string_append_characters(parser, "::", string);
346
+ katana_string_append_characters(parser, cs->data->value, string);
347
+ } else if (katana_selector_is_attribute(cs)) {
348
+ katana_string_append_characters(parser, "[", string);
349
+ if (NULL != cs->data->attribute->prefix) {
350
+ katana_string_append_characters(parser, cs->data->attribute->prefix, string);
351
+ katana_string_append_characters(parser, "|", string);
352
+ }
353
+ katana_string_append_characters(parser, cs->data->attribute->local, string);
354
+ switch (cs->match) {
355
+ case KatanaSelectorMatchAttributeExact:
356
+ katana_string_append_characters(parser, "=", string);
357
+ break;
358
+ case KatanaSelectorMatchAttributeSet:
359
+ katana_string_append_characters(parser, "]", string);
360
+ break;
361
+ case KatanaSelectorMatchAttributeList:
362
+ katana_string_append_characters(parser, "~=", string);
363
+ break;
364
+ case KatanaSelectorMatchAttributeHyphen:
365
+ katana_string_append_characters(parser, "|=", string);
366
+ break;
367
+ case KatanaSelectorMatchAttributeBegin:
368
+ katana_string_append_characters(parser, "^=", string);
369
+ break;
370
+ case KatanaSelectorMatchAttributeEnd:
371
+ katana_string_append_characters(parser, "$=", string);
372
+ break;
373
+ case KatanaSelectorMatchAttributeContain:
374
+ katana_string_append_characters(parser, "*=", string);
375
+ break;
376
+ default:
377
+ break;
378
+ }
379
+ if (cs->match != KatanaSelectorMatchAttributeSet) {
380
+ katana_string_append_characters(parser, "\"", string);
381
+ katana_string_append_characters(parser, cs->data->value, string);
382
+ katana_string_append_characters(parser, "\"", string);
383
+ if (cs->data->bits.attributeMatchType == KatanaAttributeMatchTypeCaseInsensitive)
384
+ katana_string_append_characters(parser, " i", string);
385
+ katana_string_append_characters(parser, "]", string);
386
+ }
387
+ }
388
+ if (cs->relation != KatanaSelectorRelationSubSelector || !cs->tagHistory)
389
+ break;
390
+ cs = cs->tagHistory;
391
+ }
392
+
393
+ KatanaSelector* tagHistory = cs->tagHistory;
394
+
395
+ if ( NULL != tagHistory ) {
396
+ switch (cs->relation) {
397
+ case KatanaSelectorRelationDescendant:
398
+ {
399
+ return katana_build_relation_selector_string(parser, " ", string, next, tagHistory);
400
+ }
401
+ case KatanaSelectorRelationChild:
402
+ {
403
+ return katana_build_relation_selector_string(parser, " > ", string, next, tagHistory);
404
+ }
405
+ case KatanaSelectorRelationShadowDeep:
406
+ {
407
+ return katana_build_relation_selector_string(parser, " /deep/ ", string, next, tagHistory);
408
+ }
409
+ case KatanaSelectorRelationDirectAdjacent:
410
+ {
411
+ return katana_build_relation_selector_string(parser, " + ", string, next, tagHistory);
412
+ }
413
+ case KatanaSelectorRelationIndirectAdjacent:
414
+ {
415
+ return katana_build_relation_selector_string(parser, " ~ ", string, next, tagHistory);
416
+ }
417
+ case KatanaSelectorRelationSubSelector:
418
+ {
419
+ return NULL;
420
+ }
421
+ case KatanaSelectorRelationShadowPseudo:
422
+ {
423
+ return katana_build_relation_selector_string(parser, NULL, string, next, tagHistory);
424
+ }
425
+ }
426
+ }
427
+
428
+ if ( NULL != next ) {
429
+ katana_string_append_string(parser, (KatanaParserString*)next, string);
430
+ }
431
+
432
+ return (KatanaParserString*)string;
433
+ }
434
+
435
+ unsigned calc_specificity_for_one_selector(const KatanaSelector* selector)
436
+ {
437
+ switch ( selector->match ) {
438
+ case KatanaSelectorMatchId:
439
+ return 0x10000;
440
+
441
+ case KatanaSelectorMatchPseudoClass:
442
+ case KatanaSelectorMatchAttributeExact:
443
+ case KatanaSelectorMatchClass:
444
+ case KatanaSelectorMatchAttributeSet:
445
+ case KatanaSelectorMatchAttributeList:
446
+ case KatanaSelectorMatchAttributeHyphen:
447
+ case KatanaSelectorMatchPseudoElement:
448
+ case KatanaSelectorMatchAttributeContain:
449
+ case KatanaSelectorMatchAttributeBegin:
450
+ case KatanaSelectorMatchAttributeEnd:
451
+ return 0x100;
452
+
453
+ case KatanaSelectorMatchTag:
454
+ return !strcasecmp(selector->tag->local, "*") ? 0 : 1;
455
+ case KatanaSelectorMatchUnknown:
456
+ case KatanaSelectorMatchPagePseudoClass:
457
+ return 0;
458
+ }
459
+
460
+ return 0;
461
+ }
462
+
463
+ unsigned katana_calc_specificity_for_selector(KatanaSelector* selector)
464
+ {
465
+ if ( NULL == selector ) {
466
+ return 0;
467
+ }
468
+
469
+ static const unsigned idMask = 0xff0000;
470
+ static const unsigned classMask = 0xff00;
471
+ static const unsigned elementMask = 0xff;
472
+
473
+ unsigned total = 0;
474
+ unsigned temp = 0;
475
+
476
+ for (const KatanaSelector * next = selector; next; next = next->tagHistory)
477
+ {
478
+ temp = total + calc_specificity_for_one_selector(next);
479
+
480
+ if ((temp & idMask) < (total & idMask))
481
+ total |= idMask;
482
+ else if ((temp & classMask) < (total & classMask))
483
+ total |= classMask;
484
+ else if ((temp & elementMask) < (total & elementMask))
485
+ total |= elementMask;
486
+ else
487
+ total = temp;
488
+ }
489
+
490
+ return total;
491
+ }
492
+
493
+ // Could be made smaller and faster by replacing pointer with an
494
+ // offset into a string buffer and making the bit fields smaller but
495
+ // that could not be maintained by hand.
496
+ typedef struct {
497
+ const char* string;
498
+ unsigned type:8;
499
+ } KatanaNameToPseudoStruct;
500
+
501
+ // These tables should be kept sorted.
502
+ const static KatanaNameToPseudoStruct kPseudoTypeWithoutArgumentsMap[] = {
503
+ {"-internal-list-box", KatanaPseudoListBox},
504
+ {"-internal-media-controls-cast-button", KatanaPseudoWebKitCustomElement},
505
+ {"-internal-media-controls-overlay-cast-button", KatanaPseudoWebKitCustomElement},
506
+ {"-internal-spatial-navigation-focus", KatanaPseudoSpatialNavigationFocus},
507
+ {"-webkit-any-link", KatanaPseudoAnyLink},
508
+ {"-webkit-autofill", KatanaPseudoAutofill},
509
+ {"-webkit-drag", KatanaPseudoDrag},
510
+ {"-webkit-full-page-media", KatanaPseudoFullPageMedia},
511
+ {"-webkit-full-screen", KatanaPseudoFullScreen},
512
+ {"-webkit-full-screen-ancestor", KatanaPseudoFullScreenAncestor},
513
+ {"-webkit-full-screen-document", KatanaPseudoFullScreenDocument},
514
+ {"-webkit-resizer", KatanaPseudoResizer},
515
+ {"-webkit-scrollbar", KatanaPseudoScrollbar},
516
+ {"-webkit-scrollbar-button", KatanaPseudoScrollbarButton},
517
+ {"-webkit-scrollbar-corner", KatanaPseudoScrollbarCorner},
518
+ {"-webkit-scrollbar-thumb", KatanaPseudoScrollbarThumb},
519
+ {"-webkit-scrollbar-track", KatanaPseudoScrollbarTrack},
520
+ {"-webkit-scrollbar-track-piece", KatanaPseudoScrollbarTrackPiece},
521
+ {"active", KatanaPseudoActive},
522
+ {"after", KatanaPseudoAfter},
523
+ {"backdrop", KatanaPseudoBackdrop},
524
+ {"before", KatanaPseudoBefore},
525
+ {"checked", KatanaPseudoChecked},
526
+ {"content", KatanaPseudoContent},
527
+ {"corner-present", KatanaPseudoCornerPresent},
528
+ {"cue", KatanaPseudoWebKitCustomElement},
529
+ {"decrement", KatanaPseudoDecrement},
530
+ {"default", KatanaPseudoDefault},
531
+ {"disabled", KatanaPseudoDisabled},
532
+ {"double-button", KatanaPseudoDoubleButton},
533
+ {"empty", KatanaPseudoEmpty},
534
+ {"enabled", KatanaPseudoEnabled},
535
+ {"end", KatanaPseudoEnd},
536
+ {"first", KatanaPseudoFirstPage},
537
+ {"first-child", KatanaPseudoFirstChild},
538
+ {"first-letter", KatanaPseudoFirstLetter},
539
+ {"first-line", KatanaPseudoFirstLine},
540
+ {"first-of-type", KatanaPseudoFirstOfType},
541
+ {"focus", KatanaPseudoFocus},
542
+ {"future", KatanaPseudoFutureCue},
543
+ {"horizontal", KatanaPseudoHorizontal},
544
+ {"host", KatanaPseudoHost},
545
+ {"hover", KatanaPseudoHover},
546
+ {"in-range", KatanaPseudoInRange},
547
+ {"increment", KatanaPseudoIncrement},
548
+ {"indeterminate", KatanaPseudoIndeterminate},
549
+ {"invalid", KatanaPseudoInvalid},
550
+ {"last-child", KatanaPseudoLastChild},
551
+ {"last-of-type", KatanaPseudoLastOfType},
552
+ {"left", KatanaPseudoLeftPage},
553
+ {"link", KatanaPseudoLink},
554
+ {"no-button", KatanaPseudoNoButton},
555
+ {"only-child", KatanaPseudoOnlyChild},
556
+ {"only-of-type", KatanaPseudoOnlyOfType},
557
+ {"optional", KatanaPseudoOptional},
558
+ {"out-of-range", KatanaPseudoOutOfRange},
559
+ {"past", KatanaPseudoPastCue},
560
+ {"read-only", KatanaPseudoReadOnly},
561
+ {"read-write", KatanaPseudoReadWrite},
562
+ {"required", KatanaPseudoRequired},
563
+ {"right", KatanaPseudoRightPage},
564
+ {"root", KatanaPseudoRoot},
565
+ {"scope", KatanaPseudoScope},
566
+ {"selection", KatanaPseudoSelection},
567
+ {"shadow", KatanaPseudoShadow},
568
+ {"single-button", KatanaPseudoSingleButton},
569
+ {"start", KatanaPseudoStart},
570
+ {"target", KatanaPseudoTarget},
571
+ {"unresolved", KatanaPseudoUnresolved},
572
+ {"valid", KatanaPseudoValid},
573
+ {"vertical", KatanaPseudoVertical},
574
+ {"visited", KatanaPseudoVisited},
575
+ {"window-inactive", KatanaPseudoWindowInactive},
576
+ };
577
+
578
+ const static KatanaNameToPseudoStruct kPseudoTypeWithArgumentsMap[] = {
579
+ {"-webkit-any(", KatanaPseudoAny},
580
+ {"cue(", KatanaPseudoCue},
581
+ {"host(", KatanaPseudoHost},
582
+ {"host-context(", KatanaPseudoHostContext},
583
+ {"lang(", KatanaPseudoLang},
584
+ {"not(", KatanaPseudoNot},
585
+ {"nth-child(", KatanaPseudoNthChild},
586
+ {"nth-last-child(", KatanaPseudoNthLastChild},
587
+ {"nth-last-of-type(", KatanaPseudoNthLastOfType},
588
+ {"nth-of-type(", KatanaPseudoNthOfType},
589
+ };
590
+
591
+ static const KatanaNameToPseudoStruct* lower_bound(const KatanaNameToPseudoStruct *map,
592
+ size_t count, const char* key);
593
+
594
+ static KatanaPseudoType name_to_pseudo_type(const char* name, bool hasArguments)
595
+ {
596
+ if (NULL == name)
597
+ return KatanaPseudoUnknown;
598
+
599
+ const KatanaNameToPseudoStruct* pseudoTypeMap;
600
+ size_t count;
601
+
602
+ if (hasArguments) {
603
+ pseudoTypeMap = kPseudoTypeWithArgumentsMap;
604
+ count = sizeof(kPseudoTypeWithArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
605
+ } else {
606
+ pseudoTypeMap = kPseudoTypeWithoutArgumentsMap;
607
+ count = sizeof(kPseudoTypeWithoutArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
608
+ }
609
+
610
+ const KatanaNameToPseudoStruct* match = lower_bound(pseudoTypeMap, count, name);
611
+ if ( match == (pseudoTypeMap + count)
612
+ || 0 != strcasecmp(match->string, name) )
613
+ return KatanaPseudoUnknown;
614
+
615
+ return match->type;
616
+ }
617
+
618
+ static const KatanaNameToPseudoStruct* lower_bound(const KatanaNameToPseudoStruct *array,
619
+ size_t size, const char* key) {
620
+ const KatanaNameToPseudoStruct* it;
621
+ const KatanaNameToPseudoStruct* first = array;
622
+ size_t count = size, step;
623
+ while (count > 0) {
624
+ it = first;
625
+ step = count / 2;
626
+ it += step;
627
+ if (strncmp(it->string, key, strlen(key)) < 0) {
628
+ first = ++it;
629
+ count -= step + 1;
630
+ } else count = step;
631
+ }
632
+ return first;
633
+ }
634
+
635
+ #if KATANA_PARSER_DEBUG
636
+
637
+ void test_lower_bound()
638
+ {
639
+ const KatanaNameToPseudoStruct* pseudoTypeMap;
640
+ size_t count;
641
+
642
+ pseudoTypeMap = kPseudoTypeWithArgumentsMap;
643
+ count = sizeof(kPseudoTypeWithArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
644
+
645
+ for ( size_t i = 0; i < count; i++ ) {
646
+ const KatanaNameToPseudoStruct* res = lower_bound(pseudoTypeMap, count, pseudoTypeMap[i].string);
647
+ assert(pseudoTypeMap[i].type == res->type);
648
+ }
649
+
650
+ pseudoTypeMap = kPseudoTypeWithoutArgumentsMap;
651
+ count = sizeof(kPseudoTypeWithoutArgumentsMap) / sizeof(KatanaNameToPseudoStruct);
652
+
653
+ for ( size_t i = 0; i < count; i++ ) {
654
+ const KatanaNameToPseudoStruct* res = lower_bound(pseudoTypeMap, count, pseudoTypeMap[i].string);
655
+ assert(pseudoTypeMap[i].type == res->type);
656
+ }
657
+ }
658
+
659
+ #endif // #if KATANA_PARSER_DEBUG