csspool 3.0.2 → 4.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +4 -4
- data/Manifest.txt +13 -0
- data/README.rdoc +1 -1
- data/lib/csspool/css/document.rb +6 -0
- data/lib/csspool/css/document_handler.rb +41 -7
- data/lib/csspool/css/document_query.rb +14 -0
- data/lib/csspool/css/import_rule.rb +8 -4
- data/lib/csspool/css/keyframes_block.rb +14 -0
- data/lib/csspool/css/keyframes_rule.rb +14 -0
- data/lib/csspool/css/media.rb +5 -3
- data/lib/csspool/css/namespace_rule.rb +13 -0
- data/lib/csspool/css/parser.rb +970 -425
- data/lib/csspool/css/parser.y +227 -30
- data/lib/csspool/css/tokenizer.rb +31 -7
- data/lib/csspool/css/tokenizer.rex +36 -7
- data/lib/csspool/css.rb +4 -0
- data/lib/csspool/selector.rb +5 -1
- data/lib/csspool/selectors/attribute.rb +6 -1
- data/lib/csspool/selectors/pseudo.rb +17 -0
- data/lib/csspool/selectors/pseudo_element.rb +13 -0
- data/lib/csspool/selectors/simple.rb +0 -4
- data/lib/csspool/selectors/type.rb +7 -0
- data/lib/csspool/selectors/universal.rb +7 -0
- data/lib/csspool/selectors.rb +1 -1
- data/lib/csspool/terms/math.rb +18 -0
- data/lib/csspool/terms.rb +1 -0
- data/lib/csspool/visitors/children.rb +1 -1
- data/lib/csspool/visitors/comparable.rb +8 -2
- data/lib/csspool/visitors/iterator.rb +1 -1
- data/lib/csspool/visitors/to_css.rb +41 -21
- data/lib/csspool.rb +1 -1
- data/test/_local_helper.rb +2 -0
- data/test/css/test_document_query.rb +76 -0
- data/test/css/test_import_rule.rb +1 -1
- data/test/css/test_keyframes_rule.rb +93 -0
- data/test/css/test_namespace_rule.rb +54 -0
- data/test/css/test_parser.rb +46 -1
- data/test/css/test_tokenizer.rb +0 -45
- data/test/helper.rb +6 -1
- data/test/sac/test_parser.rb +16 -3
- data/test/test_declaration.rb +39 -0
- data/test/test_parser.rb +26 -13
- data/test/test_selector.rb +205 -5
- data/test/test_term.rb +23 -0
- data/test/visitors/test_to_css.rb +34 -23
- metadata +136 -118
data/lib/csspool/css/parser.y
CHANGED
@@ -3,7 +3,15 @@ class CSSPool::CSS::Parser
|
|
3
3
|
token CHARSET_SYM IMPORT_SYM STRING SEMI IDENT S COMMA LBRACE RBRACE STAR HASH
|
4
4
|
token LSQUARE RSQUARE EQUAL INCLUDES DASHMATCH RPAREN FUNCTION GREATER PLUS
|
5
5
|
token SLASH NUMBER MINUS LENGTH PERCENTAGE EMS EXS ANGLE TIME FREQ URI
|
6
|
+
token IMPORTANT_SYM MEDIA_SYM NTH_PSEUDO_CLASS
|
7
|
+
token IMPORTANT_SYM MEDIA_SYM DOCUMENT_QUERY_SYM FUNCTION_NO_QUOTE
|
6
8
|
token IMPORTANT_SYM MEDIA_SYM
|
9
|
+
token NAMESPACE_SYM TILDE
|
10
|
+
token NAMESPACE_SYM PREFIXMATCH SUFFIXMATCH SUBSTRINGMATCH
|
11
|
+
token NAMESPACE_SYM NOT_PSEUDO_CLASS
|
12
|
+
token NAMESPACE_SYM KEYFRAMES_SYM
|
13
|
+
token NAMESPACE_SYM MATCHES_PSEUDO_CLASS
|
14
|
+
token NAMESPACE_SYM MATH
|
7
15
|
|
8
16
|
rule
|
9
17
|
document
|
@@ -14,8 +22,10 @@ rule
|
|
14
22
|
stylesheet
|
15
23
|
: charset stylesheet
|
16
24
|
| import stylesheet
|
25
|
+
| namespace stylesheet
|
17
26
|
| charset
|
18
27
|
| import
|
28
|
+
| namespace
|
19
29
|
| body
|
20
30
|
;
|
21
31
|
charset
|
@@ -34,6 +44,14 @@ rule
|
|
34
44
|
| STRING { result = Terms::String.new interpret_string val.first }
|
35
45
|
| URI { result = Terms::URI.new interpret_uri val.first }
|
36
46
|
;
|
47
|
+
namespace
|
48
|
+
: NAMESPACE_SYM ident import_location SEMI {
|
49
|
+
@handler.namespace val[1], val[2]
|
50
|
+
}
|
51
|
+
| NAMESPACE_SYM import_location SEMI {
|
52
|
+
@handler.namespace nil, val[1]
|
53
|
+
}
|
54
|
+
;
|
37
55
|
medium
|
38
56
|
: medium COMMA IDENT {
|
39
57
|
result = [val.first, Terms::Ident.new(interpret_identifier val.last)]
|
@@ -44,9 +62,15 @@ rule
|
|
44
62
|
;
|
45
63
|
body
|
46
64
|
: ruleset body
|
47
|
-
|
|
65
|
+
| conditional_rule body
|
66
|
+
| keyframes_rule body
|
48
67
|
| ruleset
|
49
|
-
|
|
68
|
+
| conditional_rule
|
69
|
+
| keyframes_rule
|
70
|
+
;
|
71
|
+
conditional_rule
|
72
|
+
: media
|
73
|
+
| document_query
|
50
74
|
;
|
51
75
|
media
|
52
76
|
: start_media body RBRACE { @handler.end_media val.first }
|
@@ -58,6 +82,60 @@ rule
|
|
58
82
|
}
|
59
83
|
| MEDIA_SYM LBRACE { result = [] }
|
60
84
|
;
|
85
|
+
document_query
|
86
|
+
: start_document_query body RBRACE { @handler.end_document_query }
|
87
|
+
| start_document_query RBRACE { @handler.end_document_query }
|
88
|
+
;
|
89
|
+
start_document_query
|
90
|
+
: DOCUMENT_QUERY_SYM url_match_fns LBRACE {
|
91
|
+
@handler.start_document_query val[1]
|
92
|
+
}
|
93
|
+
;
|
94
|
+
url_match_fns
|
95
|
+
: url_match_fn COMMA url_match_fns {
|
96
|
+
result = [val[0], val[2]].flatten
|
97
|
+
}
|
98
|
+
| url_match_fn {
|
99
|
+
result = val
|
100
|
+
}
|
101
|
+
;
|
102
|
+
url_match_fn
|
103
|
+
: function_no_quote
|
104
|
+
| function
|
105
|
+
| uri
|
106
|
+
;
|
107
|
+
keyframes_rule
|
108
|
+
: start_keyframes_rule keyframes_blocks RBRACE
|
109
|
+
| start_keyframes_rule RBRACE
|
110
|
+
;
|
111
|
+
start_keyframes_rule
|
112
|
+
: KEYFRAMES_SYM IDENT LBRACE {
|
113
|
+
@handler.start_keyframes_rule val[1]
|
114
|
+
}
|
115
|
+
;
|
116
|
+
keyframes_blocks
|
117
|
+
: keyframes_block keyframes_blocks
|
118
|
+
| keyframes_block
|
119
|
+
;
|
120
|
+
keyframes_block
|
121
|
+
: start_keyframes_block declarations RBRACE { @handler.end_keyframes_block }
|
122
|
+
| start_keyframes_block RBRACE { @handler.end_keyframes_block }
|
123
|
+
;
|
124
|
+
start_keyframes_block
|
125
|
+
: keyframes_selectors LBRACE {
|
126
|
+
@handler.start_keyframes_block val[0]
|
127
|
+
}
|
128
|
+
;
|
129
|
+
keyframes_selectors
|
130
|
+
| keyframes_selector COMMA keyframes_selectors {
|
131
|
+
result = val[0] + ', ' + val[2]
|
132
|
+
}
|
133
|
+
| keyframes_selector
|
134
|
+
;
|
135
|
+
keyframes_selector
|
136
|
+
: IDENT
|
137
|
+
| PERCENTAGE { result = val[0].strip }
|
138
|
+
;
|
61
139
|
ruleset
|
62
140
|
: start_selector declarations RBRACE {
|
63
141
|
@handler.end_selector val.first
|
@@ -97,6 +175,7 @@ rule
|
|
97
175
|
: S { result = :s }
|
98
176
|
| GREATER { result = :> }
|
99
177
|
| PLUS { result = :+ }
|
178
|
+
| TILDE { result = :~ }
|
100
179
|
;
|
101
180
|
simple_selector
|
102
181
|
: element_name hcap {
|
@@ -112,9 +191,22 @@ rule
|
|
112
191
|
result = [ss]
|
113
192
|
}
|
114
193
|
;
|
194
|
+
simple_selectors
|
195
|
+
: simple_selector COMMA simple_selectors { result = [val[0], val[2]].flatten }
|
196
|
+
| simple_selector
|
197
|
+
;
|
198
|
+
ident_with_namespace
|
199
|
+
: IDENT { result = [interpret_identifier(val[0]), nil] }
|
200
|
+
| IDENT '|' IDENT { result = [interpret_identifier(val[2]), interpret_identifier(val[0])] }
|
201
|
+
| '|' IDENT { result = [interpret_identifier(val[1]), nil] }
|
202
|
+
| STAR '|' IDENT { result = [interpret_identifier(val[2]), '*'] }
|
203
|
+
;
|
115
204
|
element_name
|
116
|
-
:
|
117
|
-
| STAR
|
205
|
+
: ident_with_namespace { result = Selectors::Type.new val.first[0], nil, val.first[1] }
|
206
|
+
| STAR { result = Selectors::Universal.new val.first }
|
207
|
+
| '|' STAR { result = Selectors::Universal.new val[1] }
|
208
|
+
| STAR '|' STAR { result = Selectors::Universal.new val[2], nil, val[0] }
|
209
|
+
| IDENT '|' STAR { result = Selectors::Universal.new val[2], nil, interpret_identifier(val[0]) }
|
118
210
|
;
|
119
211
|
hcap
|
120
212
|
: hash { result = val }
|
@@ -136,60 +228,118 @@ rule
|
|
136
228
|
}
|
137
229
|
;
|
138
230
|
attrib
|
139
|
-
: LSQUARE
|
231
|
+
: LSQUARE ident_with_namespace EQUAL IDENT RSQUARE {
|
140
232
|
result = Selectors::Attribute.new(
|
141
|
-
|
233
|
+
val[1][0],
|
142
234
|
interpret_identifier(val[3]),
|
143
|
-
Selectors::Attribute::EQUALS
|
235
|
+
Selectors::Attribute::EQUALS,
|
236
|
+
val[1][1]
|
144
237
|
)
|
145
238
|
}
|
146
|
-
| LSQUARE
|
239
|
+
| LSQUARE ident_with_namespace EQUAL STRING RSQUARE {
|
147
240
|
result = Selectors::Attribute.new(
|
148
|
-
|
241
|
+
val[1][0],
|
149
242
|
interpret_string(val[3]),
|
150
|
-
Selectors::Attribute::EQUALS
|
243
|
+
Selectors::Attribute::EQUALS,
|
244
|
+
val[1][1]
|
151
245
|
)
|
152
246
|
}
|
153
|
-
| LSQUARE
|
247
|
+
| LSQUARE ident_with_namespace INCLUDES STRING RSQUARE {
|
154
248
|
result = Selectors::Attribute.new(
|
155
|
-
|
249
|
+
val[1][0],
|
156
250
|
interpret_string(val[3]),
|
157
|
-
Selectors::Attribute::INCLUDES
|
251
|
+
Selectors::Attribute::INCLUDES,
|
252
|
+
val[1][1]
|
158
253
|
)
|
159
254
|
}
|
160
|
-
| LSQUARE
|
255
|
+
| LSQUARE ident_with_namespace INCLUDES IDENT RSQUARE {
|
161
256
|
result = Selectors::Attribute.new(
|
162
|
-
|
257
|
+
val[1][0],
|
163
258
|
interpret_identifier(val[3]),
|
164
|
-
Selectors::Attribute::INCLUDES
|
259
|
+
Selectors::Attribute::INCLUDES,
|
260
|
+
val[1][1]
|
165
261
|
)
|
166
262
|
}
|
167
|
-
| LSQUARE
|
263
|
+
| LSQUARE ident_with_namespace DASHMATCH IDENT RSQUARE {
|
168
264
|
result = Selectors::Attribute.new(
|
169
|
-
|
265
|
+
val[1][0],
|
170
266
|
interpret_identifier(val[3]),
|
171
|
-
Selectors::Attribute::DASHMATCH
|
267
|
+
Selectors::Attribute::DASHMATCH,
|
268
|
+
val[1][1]
|
172
269
|
)
|
173
270
|
}
|
174
|
-
| LSQUARE
|
271
|
+
| LSQUARE ident_with_namespace DASHMATCH STRING RSQUARE {
|
175
272
|
result = Selectors::Attribute.new(
|
176
|
-
|
273
|
+
val[1][0],
|
177
274
|
interpret_string(val[3]),
|
178
|
-
Selectors::Attribute::DASHMATCH
|
275
|
+
Selectors::Attribute::DASHMATCH,
|
276
|
+
val[1][1]
|
179
277
|
)
|
180
278
|
}
|
181
|
-
| LSQUARE IDENT RSQUARE {
|
279
|
+
| LSQUARE ident_with_namespace PREFIXMATCH IDENT RSQUARE {
|
182
280
|
result = Selectors::Attribute.new(
|
183
|
-
|
281
|
+
val[1][0],
|
282
|
+
interpret_identifier(val[3]),
|
283
|
+
Selectors::Attribute::PREFIXMATCH,
|
284
|
+
val[1][1]
|
285
|
+
)
|
286
|
+
}
|
287
|
+
| LSQUARE ident_with_namespace PREFIXMATCH STRING RSQUARE {
|
288
|
+
result = Selectors::Attribute.new(
|
289
|
+
val[1][0],
|
290
|
+
interpret_string(val[3]),
|
291
|
+
Selectors::Attribute::PREFIXMATCH,
|
292
|
+
val[1][1]
|
293
|
+
)
|
294
|
+
}
|
295
|
+
| LSQUARE ident_with_namespace SUFFIXMATCH IDENT RSQUARE {
|
296
|
+
result = Selectors::Attribute.new(
|
297
|
+
val[1][0],
|
298
|
+
interpret_identifier(val[3]),
|
299
|
+
Selectors::Attribute::SUFFIXMATCH,
|
300
|
+
val[1][1]
|
301
|
+
)
|
302
|
+
}
|
303
|
+
| LSQUARE ident_with_namespace SUFFIXMATCH STRING RSQUARE {
|
304
|
+
result = Selectors::Attribute.new(
|
305
|
+
val[1][0],
|
306
|
+
interpret_string(val[3]),
|
307
|
+
Selectors::Attribute::SUFFIXMATCH,
|
308
|
+
val[1][1]
|
309
|
+
)
|
310
|
+
}
|
311
|
+
| LSQUARE ident_with_namespace SUBSTRINGMATCH IDENT RSQUARE {
|
312
|
+
result = Selectors::Attribute.new(
|
313
|
+
val[1][0],
|
314
|
+
interpret_identifier(val[3]),
|
315
|
+
Selectors::Attribute::SUBSTRINGMATCH,
|
316
|
+
val[1][1]
|
317
|
+
)
|
318
|
+
}
|
319
|
+
| LSQUARE ident_with_namespace SUBSTRINGMATCH STRING RSQUARE {
|
320
|
+
result = Selectors::Attribute.new(
|
321
|
+
val[1][0],
|
322
|
+
interpret_string(val[3]),
|
323
|
+
Selectors::Attribute::SUBSTRINGMATCH,
|
324
|
+
val[1][1]
|
325
|
+
)
|
326
|
+
}
|
327
|
+
| LSQUARE ident_with_namespace RSQUARE {
|
328
|
+
result = Selectors::Attribute.new(
|
329
|
+
val[1][0],
|
184
330
|
nil,
|
185
|
-
Selectors::Attribute::SET
|
331
|
+
Selectors::Attribute::SET,
|
332
|
+
val[1][1]
|
186
333
|
)
|
187
334
|
}
|
188
335
|
;
|
189
336
|
pseudo
|
190
337
|
: ':' IDENT {
|
191
|
-
result = Selectors::
|
192
|
-
|
338
|
+
result = Selectors::pseudo interpret_identifier(val[1])
|
339
|
+
}
|
340
|
+
| ':' ':' IDENT {
|
341
|
+
result = Selectors::PseudoElement.new(
|
342
|
+
interpret_identifier(val[2])
|
193
343
|
)
|
194
344
|
}
|
195
345
|
| ':' FUNCTION RPAREN {
|
@@ -204,11 +354,36 @@ rule
|
|
204
354
|
interpret_identifier(val[2])
|
205
355
|
)
|
206
356
|
}
|
357
|
+
| ':' NOT_PSEUDO_CLASS simple_selector RPAREN {
|
358
|
+
result = Selectors::PseudoClass.new(
|
359
|
+
'not',
|
360
|
+
val[2].first.to_s
|
361
|
+
)
|
362
|
+
}
|
363
|
+
| ':' NTH_PSEUDO_CLASS {
|
364
|
+
result = Selectors::PseudoClass.new(
|
365
|
+
interpret_identifier(val[1].sub(/\(.*/, '')),
|
366
|
+
interpret_identifier(val[1].sub(/.*\(/, '').sub(/\).*/, ''))
|
367
|
+
)
|
368
|
+
}
|
369
|
+
| ':' MATCHES_PSEUDO_CLASS simple_selectors RPAREN {
|
370
|
+
result = Selectors::PseudoClass.new(
|
371
|
+
val[1].split('(').first.strip,
|
372
|
+
val[2].join(', ')
|
373
|
+
)
|
374
|
+
}
|
375
|
+
;
|
376
|
+
# declarations can be separated by one *or more* semicolons. semi-colons at the start or end of a ruleset are also allowed
|
377
|
+
one_or_more_semis
|
378
|
+
: SEMI
|
379
|
+
| SEMI one_or_more_semis
|
207
380
|
;
|
208
381
|
declarations
|
209
|
-
: declaration
|
210
|
-
|
|
382
|
+
: declaration one_or_more_semis declarations
|
383
|
+
| one_or_more_semis declarations
|
384
|
+
| declaration one_or_more_semis
|
211
385
|
| declaration
|
386
|
+
| one_or_more_semis
|
212
387
|
;
|
213
388
|
declaration
|
214
389
|
: property ':' expr prio
|
@@ -247,6 +422,7 @@ rule
|
|
247
422
|
| string
|
248
423
|
| uri
|
249
424
|
| hexcolor
|
425
|
+
| math
|
250
426
|
| function
|
251
427
|
;
|
252
428
|
function
|
@@ -260,6 +436,22 @@ rule
|
|
260
436
|
end
|
261
437
|
}
|
262
438
|
;
|
439
|
+
function_no_quote
|
440
|
+
: function_no_quote S { result = val.first }
|
441
|
+
| FUNCTION_NO_QUOTE {
|
442
|
+
parts = val.first.split('(')
|
443
|
+
name = interpret_identifier parts.first
|
444
|
+
result = Terms::Function.new(name, [Terms::String.new(interpret_string_no_quote(parts.last))])
|
445
|
+
}
|
446
|
+
;
|
447
|
+
math
|
448
|
+
: MATH {
|
449
|
+
parts = val.first.split('(', 2)
|
450
|
+
name = parts[0].strip
|
451
|
+
expression = parts[1][0..parts[1].rindex(')')-1].strip
|
452
|
+
result = Terms::Math.new(name, expression)
|
453
|
+
}
|
454
|
+
;
|
263
455
|
hexcolor
|
264
456
|
: hexcolor S { result = val.first }
|
265
457
|
| HASH { result = Terms::Hash.new val.first.sub(/^#/, '') }
|
@@ -267,6 +459,7 @@ rule
|
|
267
459
|
uri
|
268
460
|
: uri S { result = val.first }
|
269
461
|
| URI { result = Terms::URI.new interpret_uri val.first }
|
462
|
+
;
|
270
463
|
string
|
271
464
|
: string S { result = val.first }
|
272
465
|
| STRING { result = Terms::String.new interpret_string val.first }
|
@@ -326,7 +519,11 @@ def interpret_identifier s
|
|
326
519
|
end
|
327
520
|
|
328
521
|
def interpret_uri s
|
329
|
-
interpret_escapes s.match(/^url\((.*)\)$/
|
522
|
+
interpret_escapes s.match(/^url\((.*)\)$/mui)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
|
523
|
+
end
|
524
|
+
|
525
|
+
def interpret_string_no_quote s
|
526
|
+
interpret_escapes s.match(/^(.*)\)$/mu)[1].strip.match(/^(['"]?)((?:\\.|.)*)\1$/mu)[2]
|
330
527
|
end
|
331
528
|
|
332
529
|
def interpret_string s
|
@@ -58,7 +58,7 @@ class Tokenizer < Parser
|
|
58
58
|
token = case @state
|
59
59
|
when nil
|
60
60
|
case
|
61
|
-
when (text = @ss.scan(/url\([\s]*("([^\n\r\f\\"]
|
61
|
+
when (text = @ss.scan(/url\([\s]*("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')[\s]*\)/i))
|
62
62
|
action { [:URI, st(text)] }
|
63
63
|
|
64
64
|
when (text = @ss.scan(/url\([\s]*([!#\$%&*-~]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*[\s]*\)/i))
|
@@ -70,6 +70,24 @@ class Tokenizer < Parser
|
|
70
70
|
when (text = @ss.scan(/[\s]*\/\*(.|[\s]*)*?\*\/[\s]*/i))
|
71
71
|
action { next_token }
|
72
72
|
|
73
|
+
when (text = @ss.scan(/not\(\s*/i))
|
74
|
+
action { [:NOT_PSEUDO_CLASS, st(text)] }
|
75
|
+
|
76
|
+
when (text = @ss.scan(/(nth\-child|nth\-last\-child|nth\-of\-type|nth\-last\-of\-type)\([\s]*([\+\-]?[0-9]*n([\s]*[\+\-][\s]*[0-9]+)?|[\+\-]?[0-9]+|odd|even)[\s]*\)/i))
|
77
|
+
action { [:NTH_PSEUDO_CLASS, st(text)] }
|
78
|
+
|
79
|
+
when (text = @ss.scan(/(\-[A-Za-z]+\-)?(matches|any)\(\s*/i))
|
80
|
+
action { [:MATCHES_PSEUDO_CLASS, st(text)] }
|
81
|
+
|
82
|
+
when (text = @ss.scan(/(domain|url\-prefix)\([\s]*("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')[\s]*\)/i))
|
83
|
+
action { [:FUNCTION_NO_QUOTE, st(text)] }
|
84
|
+
|
85
|
+
when (text = @ss.scan(/(domain|url\-prefix)\([\s]*([!#\$%&*-~]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*[\s]*\)/i))
|
86
|
+
action { [:FUNCTION_NO_QUOTE, st(text)] }
|
87
|
+
|
88
|
+
when (text = @ss.scan(/[\s]*(\-[A-Za-z]+\-)?calc\([\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)([\s]*(\*[\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)|\/[\s]*([0-9]*\.[0-9]+|[0-9]+)))*([\s]+[\+\-][\s]+(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)([\s]*(\*[\s]*(([0-9]*\.[0-9]+|[0-9]+)|([0-9]*\.[0-9]+|[0-9]+)(px|cm|mm|in|pt|pc)|([0-9]*\.[0-9]+|[0-9]+)%|([0-9]*\.[0-9]+|[0-9]+)em|([0-9]*\.[0-9]+|[0-9]+)ex)|\/[\s]*([0-9]*\.[0-9]+|[0-9]+)))*)*[\s]*\)[\s]*[\s]*/i))
|
89
|
+
action { [:MATH, st(text)] }
|
90
|
+
|
73
91
|
when (text = @ss.scan(/[-@]?([_A-Za-z]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])([_A-Za-z0-9-]|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f]|[.])*\(\s*/i))
|
74
92
|
action { [:FUNCTION, st(text)] }
|
75
93
|
|
@@ -85,6 +103,15 @@ class Tokenizer < Parser
|
|
85
103
|
when (text = @ss.scan(/[\s]*@media[\s]*/i))
|
86
104
|
action { [:MEDIA_SYM, st(text)] }
|
87
105
|
|
106
|
+
when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?document[\s]*/i))
|
107
|
+
action { [:DOCUMENT_QUERY_SYM, st(text)] }
|
108
|
+
|
109
|
+
when (text = @ss.scan(/[\s]*@namespace[\s]*/i))
|
110
|
+
action { [:NAMESPACE_SYM, st(text)] }
|
111
|
+
|
112
|
+
when (text = @ss.scan(/[\s]*@(\-[A-Za-z]+\-)?keyframes[\s]*/i))
|
113
|
+
action { [:KEYFRAMES_SYM, st(text)] }
|
114
|
+
|
88
115
|
when (text = @ss.scan(/[\s]*!([\s]*|[\s]*\/\*(.|[\s]*)*?\*\/[\s]*)important[\s]*/i))
|
89
116
|
action { [:IMPORTANT_SYM, st(text)] }
|
90
117
|
|
@@ -118,7 +145,7 @@ class Tokenizer < Parser
|
|
118
145
|
when (text = @ss.scan(/[\s]*\)/i))
|
119
146
|
action { [:RPAREN, st(text)] }
|
120
147
|
|
121
|
-
when (text = @ss.scan(
|
148
|
+
when (text = @ss.scan(/\[[\s]*/i))
|
122
149
|
action { [:LSQUARE, st(text)] }
|
123
150
|
|
124
151
|
when (text = @ss.scan(/[\s]*\]/i))
|
@@ -148,9 +175,6 @@ class Tokenizer < Parser
|
|
148
175
|
when (text = @ss.scan(/[\s]*~[\s]*/i))
|
149
176
|
action { [:TILDE, st(text)] }
|
150
177
|
|
151
|
-
when (text = @ss.scan(/\:not\([\s]*/i))
|
152
|
-
action { [:NOT, st(text)] }
|
153
|
-
|
154
178
|
when (text = @ss.scan(/[\s]*([0-9]*\.[0-9]+|[0-9]+)em[\s]*/i))
|
155
179
|
action { [:EMS, st(text)] }
|
156
180
|
|
@@ -196,10 +220,10 @@ class Tokenizer < Parser
|
|
196
220
|
when (text = @ss.scan(/[\s]+/i))
|
197
221
|
action { [:S, st(text)] }
|
198
222
|
|
199
|
-
when (text = @ss.scan(/("([^\n\r\f\\"]
|
223
|
+
when (text = @ss.scan(/("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*"|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*')/i))
|
200
224
|
action { [:STRING, st(text)] }
|
201
225
|
|
202
|
-
when (text = @ss.scan(/("([^\n\r\f\\"]
|
226
|
+
when (text = @ss.scan(/("([^\n\r\f\\"]|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*|'([^\n\r\f\\']|\\(\n|\r\n|\r|\f)|[^\0-\177]|\\[0-9A-Fa-f]{1,6}(\r\n|[\s])?|\\[^\n\r\f0-9A-Fa-f])*)/i))
|
203
227
|
action { [:INVALID, st(text)] }
|
204
228
|
|
205
229
|
when (text = @ss.scan(/./i))
|
@@ -3,11 +3,17 @@ module CSS
|
|
3
3
|
class Tokenizer < Parser
|
4
4
|
|
5
5
|
macro
|
6
|
-
nl \n|\r\n|\r|\f
|
6
|
+
nl (\n|\r\n|\r|\f)
|
7
7
|
w [\s]*
|
8
8
|
nonascii [^\0-\177]
|
9
9
|
num ([0-9]*\.[0-9]+|[0-9]+)
|
10
|
+
length {num}(px|cm|mm|in|pt|pc)
|
11
|
+
percentage {num}%
|
12
|
+
ems {num}em
|
13
|
+
exs {num}ex
|
10
14
|
unicode \\[0-9A-Fa-f]{1,6}(\r\n|[\s])?
|
15
|
+
nth ([\+\-]?[0-9]*n({w}[\+\-]{w}[0-9]+)?|[\+\-]?[0-9]+|odd|even)
|
16
|
+
vendorprefix \-[A-Za-z]+\-
|
11
17
|
|
12
18
|
escape {unicode}|\\[^\n\r\f0-9A-Fa-f]
|
13
19
|
nmchar [_A-Za-z0-9-]|{nonascii}|{escape}
|
@@ -23,6 +29,12 @@ macro
|
|
23
29
|
invalid ({invalid1}|{invalid2})
|
24
30
|
comment \/\*(.|{w})*?\*\/
|
25
31
|
|
32
|
+
unit ({num}|{length}|{percentage}|{ems}|{exs})
|
33
|
+
product {unit}({w}(\*{w}{unit}|\/{w}{num}))*
|
34
|
+
sum {product}([\s]+[\+\-][\s]+{product})*
|
35
|
+
calc ({vendorprefix})?calc\({w}{sum}{w}\)
|
36
|
+
math {calc}{w}
|
37
|
+
|
26
38
|
rule
|
27
39
|
|
28
40
|
# [:state] pattern [actions]
|
@@ -32,11 +44,29 @@ rule
|
|
32
44
|
U\+[0-9a-fA-F?]{1,6}(-[0-9a-fA-F]{1,6})? {[:UNICODE_RANGE, st(text)] }
|
33
45
|
{w}{comment}{w} { next_token }
|
34
46
|
|
47
|
+
# this one takes a selector as a parameter
|
48
|
+
not\(\s* { [:NOT_PSEUDO_CLASS, st(text)] }
|
49
|
+
|
50
|
+
# this one takes an "nth" value
|
51
|
+
(nth\-child|nth\-last\-child|nth\-of\-type|nth\-last\-of\-type)\({w}{nth}{w}\) { [:NTH_PSEUDO_CLASS, st(text)] }
|
52
|
+
|
53
|
+
# this one takes a comma-separated list of simple selectors as a parameter
|
54
|
+
({vendorprefix})?(matches|any)\(\s* { [:MATCHES_PSEUDO_CLASS, st(text)] }
|
55
|
+
|
56
|
+
# functions that can take an unquoted string parameter
|
57
|
+
(domain|url\-prefix)\({w}{string}{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
|
58
|
+
(domain|url\-prefix)\({w}([!#\$%&*-~]|{nonascii}|{escape})*{w}\) { [:FUNCTION_NO_QUOTE, st(text)] }
|
59
|
+
|
60
|
+
{w}{math}{w} { [:MATH, st(text)] }
|
61
|
+
|
35
62
|
{func}\(\s* { [:FUNCTION, st(text)] }
|
36
63
|
{w}@import{w} { [:IMPORT_SYM, st(text)] }
|
37
64
|
{w}@page{w} { [:PAGE_SYM, st(text)] }
|
38
65
|
{w}@charset{w} { [:CHARSET_SYM, st(text)] }
|
39
66
|
{w}@media{w} { [:MEDIA_SYM, st(text)] }
|
67
|
+
{w}@({vendorprefix})?document{w} { [:DOCUMENT_QUERY_SYM, st(text)] }
|
68
|
+
{w}@namespace{w} { [:NAMESPACE_SYM, st(text)] }
|
69
|
+
{w}@({vendorprefix})?keyframes{w} { [:KEYFRAMES_SYM, st(text)] }
|
40
70
|
{w}!({w}|{w}{comment}{w})important{w} { [:IMPORTANT_SYM, st(text)] }
|
41
71
|
{ident} { [:IDENT, st(text)] }
|
42
72
|
\#{name} { [:HASH, st(text)] }
|
@@ -48,7 +78,7 @@ rule
|
|
48
78
|
{w}!={w} { [:NOT_EQUAL, st(text)] }
|
49
79
|
{w}={w} { [:EQUAL, st(text)] }
|
50
80
|
{w}\) { [:RPAREN, st(text)] }
|
51
|
-
|
81
|
+
\[{w} { [:LSQUARE, st(text)] }
|
52
82
|
{w}\] { [:RSQUARE, st(text)] }
|
53
83
|
{w}\+{w} { [:PLUS, st(text)] }
|
54
84
|
{w}\{{w} { [:LBRACE, st(text)] }
|
@@ -58,16 +88,15 @@ rule
|
|
58
88
|
{w};{w} { [:SEMI, st(';')] }
|
59
89
|
\* { [:STAR, st(text)] }
|
60
90
|
{w}~{w} { [:TILDE, st(text)] }
|
61
|
-
|
62
|
-
{w}{
|
63
|
-
{w}{num}ex{w} { [:EXS, st(text)] }
|
91
|
+
{w}{ems}{w} { [:EMS, st(text)] }
|
92
|
+
{w}{exs}{w} { [:EXS, st(text)] }
|
64
93
|
|
65
|
-
{w}{
|
94
|
+
{w}{length}{w} { [:LENGTH, st(text)] }
|
66
95
|
{w}{num}(deg|rad|grad){w} { [:ANGLE, st(text)] }
|
67
96
|
{w}{num}(ms|s){w} { [:TIME, st(text)] }
|
68
97
|
{w}{num}[k]?hz{w} { [:FREQ, st(text)] }
|
69
98
|
|
70
|
-
{w}{
|
99
|
+
{w}{percentage}{w} { [:PERCENTAGE, st(text)] }
|
71
100
|
{w}{num}{w} { [:NUMBER, st(text)] }
|
72
101
|
{w}\/\/{w} { [:DOUBLESLASH, st(text)] }
|
73
102
|
{w}\/{w} { [:SLASH, st('/')] }
|
data/lib/csspool/css.rb
CHANGED
@@ -6,3 +6,7 @@ require 'csspool/css/rule_set'
|
|
6
6
|
require 'csspool/css/declaration'
|
7
7
|
require 'csspool/css/document'
|
8
8
|
require 'csspool/css/document_handler'
|
9
|
+
require 'csspool/css/document_query'
|
10
|
+
require 'csspool/css/namespace_rule'
|
11
|
+
require 'csspool/css/keyframes_rule'
|
12
|
+
require 'csspool/css/keyframes_block'
|
data/lib/csspool/selector.rb
CHANGED
@@ -17,10 +17,14 @@ module CSSPool
|
|
17
17
|
def specificity
|
18
18
|
a = b = c = 0
|
19
19
|
simple_selectors.each do |s|
|
20
|
-
|
20
|
+
if !s.name.nil?
|
21
|
+
c += 1
|
22
|
+
end
|
21
23
|
s.additional_selectors.each do |additional_selector|
|
22
24
|
if Selectors::Id === additional_selector
|
23
25
|
a += 1
|
26
|
+
elsif Selectors::PseudoElement === additional_selector
|
27
|
+
c += 1
|
24
28
|
else
|
25
29
|
b += 1
|
26
30
|
end
|
@@ -4,17 +4,22 @@ module CSSPool
|
|
4
4
|
attr_accessor :name
|
5
5
|
attr_accessor :value
|
6
6
|
attr_accessor :match_way
|
7
|
+
attr_accessor :namespace
|
7
8
|
|
8
9
|
NO_MATCH = 0
|
9
10
|
SET = 1
|
10
11
|
EQUALS = 2
|
11
12
|
INCLUDES = 3
|
12
13
|
DASHMATCH = 4
|
14
|
+
PREFIXMATCH = 5
|
15
|
+
SUFFIXMATCH = 6
|
16
|
+
SUBSTRINGMATCH = 7
|
13
17
|
|
14
|
-
def initialize name, value, match_way
|
18
|
+
def initialize name, value, match_way, namespace = nil
|
15
19
|
@name = name
|
16
20
|
@value = value
|
17
21
|
@match_way = match_way
|
22
|
+
@namespace = namespace
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'csspool/selectors/pseudo_class'
|
2
|
+
require 'csspool/selectors/pseudo_element'
|
3
|
+
|
4
|
+
module CSSPool
|
5
|
+
module Selectors
|
6
|
+
def Selectors.pseudo name
|
7
|
+
# FIXME: This is a bit of an ugly solution. Should be able to handle it
|
8
|
+
# more elegantly, and without calling out css2
|
9
|
+
css2_pseudo_elements =
|
10
|
+
if %w{after before first-letter first-line}.include? name
|
11
|
+
PseudoElement.new name, true
|
12
|
+
else
|
13
|
+
PseudoClass.new name
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/csspool/selectors.rb
CHANGED