crass 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/HISTORY.md +22 -1
- data/LICENSE +1 -1
- data/README.md +64 -72
- data/Rakefile +4 -0
- data/crass.gemspec +2 -2
- data/lib/crass.rb +1 -1
- data/lib/crass/parser.rb +231 -96
- data/lib/crass/scanner.rb +21 -21
- data/lib/crass/token-scanner.rb +8 -1
- data/lib/crass/tokenizer.rb +133 -131
- data/lib/crass/version.rb +1 -1
- data/test/css-parsing-tests/An+B.json +156 -0
- data/test/css-parsing-tests/LICENSE +8 -0
- data/test/css-parsing-tests/README.rst +301 -0
- data/test/css-parsing-tests/color3.json +142 -0
- data/test/css-parsing-tests/color3_hsl.json +3890 -0
- data/test/css-parsing-tests/color3_keywords.json +803 -0
- data/test/css-parsing-tests/component_value_list.json +432 -0
- data/test/css-parsing-tests/declaration_list.json +44 -0
- data/test/css-parsing-tests/make_color3_hsl.py +17 -0
- data/test/css-parsing-tests/make_color3_keywords.py +191 -0
- data/test/css-parsing-tests/one_component_value.json +27 -0
- data/test/css-parsing-tests/one_declaration.json +46 -0
- data/test/css-parsing-tests/one_rule.json +36 -0
- data/test/css-parsing-tests/rule_list.json +48 -0
- data/test/css-parsing-tests/stylesheet.json +44 -0
- data/test/css-parsing-tests/stylesheet_bytes.json +146 -0
- data/test/shared/parse_rules.rb +377 -434
- data/test/support/common.rb +124 -0
- data/test/support/serialization/animate.css +3158 -0
- data/test/support/serialization/html5-boilerplate.css +268 -0
- data/test/support/serialization/misc.css +9 -0
- data/test/test_css_parsing_tests.rb +150 -0
- data/test/test_parse_properties.rb +136 -211
- data/test/test_parse_rules.rb +0 -52
- data/test/test_parse_stylesheet.rb +0 -39
- data/test/test_serialization.rb +13 -4
- metadata +44 -7
- data/test/test_tokenizer.rb +0 -1562
data/lib/crass/scanner.rb
CHANGED
@@ -18,12 +18,13 @@ module Crass
|
|
18
18
|
# position, not a byte position, so it accounts for multi-byte characters.
|
19
19
|
attr_accessor :pos
|
20
20
|
|
21
|
+
# String being scanned.
|
22
|
+
attr_reader :string
|
23
|
+
|
21
24
|
# Creates a Scanner instance for the given _input_ string or IO instance.
|
22
25
|
def initialize(input)
|
23
|
-
string
|
24
|
-
|
25
|
-
@chars = string.chars.to_a
|
26
|
-
@scanner = StringScanner.new(string)
|
26
|
+
@string = input.is_a?(IO) ? input.read : input.to_s
|
27
|
+
@scanner = StringScanner.new(@string)
|
27
28
|
|
28
29
|
reset
|
29
30
|
end
|
@@ -31,11 +32,11 @@ module Crass
|
|
31
32
|
# Consumes the next character and returns it, advancing the pointer, or
|
32
33
|
# an empty string if the end of the string has been reached.
|
33
34
|
def consume
|
34
|
-
if @pos
|
35
|
-
|
36
|
-
else
|
37
|
-
@pos += 1
|
35
|
+
if @pos < @len
|
36
|
+
@pos += 1
|
38
37
|
@current = @scanner.getch
|
38
|
+
else
|
39
|
+
''
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
@@ -43,8 +44,12 @@ module Crass
|
|
43
44
|
# the end of the string. Returns an empty string is the end of the string
|
44
45
|
# has already been reached.
|
45
46
|
def consume_rest
|
46
|
-
|
47
|
-
|
47
|
+
result = @scanner.rest
|
48
|
+
|
49
|
+
@current = result[-1]
|
50
|
+
@pos = @len
|
51
|
+
|
52
|
+
result
|
48
53
|
end
|
49
54
|
|
50
55
|
# Returns `true` if the end of the string has been reached, `false`
|
@@ -62,8 +67,8 @@ module Crass
|
|
62
67
|
# Returns the substring between {#marker} and {#pos}, without altering the
|
63
68
|
# pointer.
|
64
69
|
def marked
|
65
|
-
if result = @
|
66
|
-
result
|
70
|
+
if result = @string[@marker, @pos - @marker]
|
71
|
+
result
|
67
72
|
else
|
68
73
|
''
|
69
74
|
end
|
@@ -73,7 +78,7 @@ module Crass
|
|
73
78
|
# doesn't consume them. The number of characters returned may be less than
|
74
79
|
# _length_ if the end of the string is reached.
|
75
80
|
def peek(length = 1)
|
76
|
-
@
|
81
|
+
@string[pos, length]
|
77
82
|
end
|
78
83
|
|
79
84
|
# Moves the pointer back one character without changing the value of
|
@@ -87,7 +92,7 @@ module Crass
|
|
87
92
|
# Resets the pointer to the beginning of the string.
|
88
93
|
def reset
|
89
94
|
@current = nil
|
90
|
-
@len = @
|
95
|
+
@len = @string.size
|
91
96
|
@marker = 0
|
92
97
|
@pos = 0
|
93
98
|
end
|
@@ -98,7 +103,7 @@ module Crass
|
|
98
103
|
def scan(pattern)
|
99
104
|
if match = @scanner.scan(pattern)
|
100
105
|
@pos += match.size
|
101
|
-
@current =
|
106
|
+
@current = match[-1]
|
102
107
|
end
|
103
108
|
|
104
109
|
match
|
@@ -110,16 +115,11 @@ module Crass
|
|
110
115
|
def scan_until(pattern)
|
111
116
|
if match = @scanner.scan_until(pattern)
|
112
117
|
@pos += match.size
|
113
|
-
@current =
|
118
|
+
@current = match[-1]
|
114
119
|
end
|
115
120
|
|
116
121
|
match
|
117
122
|
end
|
118
123
|
end
|
119
124
|
|
120
|
-
# Returns the string being scanned.
|
121
|
-
def string
|
122
|
-
@scanner.string
|
123
|
-
end
|
124
|
-
|
125
125
|
end
|
data/lib/crass/token-scanner.rb
CHANGED
@@ -19,13 +19,20 @@ module Crass
|
|
19
19
|
@tokens[start...@pos] || []
|
20
20
|
end
|
21
21
|
|
22
|
-
# Consumes the next token and returns it, advancing the pointer.
|
22
|
+
# Consumes the next token and returns it, advancing the pointer. Returns
|
23
|
+
# `nil` if there is no next token.
|
23
24
|
def consume
|
24
25
|
@current = @tokens[@pos]
|
25
26
|
@pos += 1 if @current
|
26
27
|
@current
|
27
28
|
end
|
28
29
|
|
30
|
+
# Returns the next token without consuming it, or `nil` if there is no next
|
31
|
+
# token.
|
32
|
+
def peek
|
33
|
+
@tokens[@pos]
|
34
|
+
end
|
35
|
+
|
29
36
|
# Reconsumes the current token, moving the pointer back one position.
|
30
37
|
#
|
31
38
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#reconsume-the-current-input-token
|
data/lib/crass/tokenizer.rb
CHANGED
@@ -5,7 +5,7 @@ module Crass
|
|
5
5
|
|
6
6
|
# Tokenizes a CSS string.
|
7
7
|
#
|
8
|
-
# http://
|
8
|
+
# 4. http://dev.w3.org/csswg/css-syntax/#tokenization
|
9
9
|
class Tokenizer
|
10
10
|
RE_COMMENT_CLOSE = /\*\//
|
11
11
|
RE_DIGIT = /[0-9]+/
|
@@ -30,9 +30,11 @@ module Crass
|
|
30
30
|
)?
|
31
31
|
\z/x
|
32
32
|
|
33
|
+
RE_QUOTED_URL_START = /\A[\n\u0009\u0020]?["']/
|
33
34
|
RE_UNICODE_RANGE_START = /\+(?:[0-9A-Fa-f]|\?)/
|
34
35
|
RE_UNICODE_RANGE_END = /-[0-9A-Fa-f]/
|
35
36
|
RE_WHITESPACE = /[\n\u0009\u0020]+/
|
37
|
+
RE_WHITESPACE_ANCHORED = /\A[\n\u0009\u0020]+\z/
|
36
38
|
|
37
39
|
# -- Class Methods ---------------------------------------------------------
|
38
40
|
|
@@ -64,23 +66,34 @@ module Crass
|
|
64
66
|
|
65
67
|
# Consumes a token and returns the token that was consumed.
|
66
68
|
#
|
67
|
-
# http://
|
69
|
+
# 4.3.1. http://dev.w3.org/csswg/css-syntax/#consume-a-token
|
68
70
|
def consume
|
69
71
|
return nil if @s.eos?
|
70
72
|
|
71
73
|
@s.mark
|
74
|
+
|
75
|
+
# Consume comments.
|
76
|
+
if comment_token = consume_comments
|
77
|
+
if @options[:preserve_comments]
|
78
|
+
return comment_token
|
79
|
+
else
|
80
|
+
return consume
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Consume whitespace.
|
72
85
|
return create_token(:whitespace) if @s.scan(RE_WHITESPACE)
|
73
86
|
|
74
87
|
char = @s.consume
|
75
88
|
|
76
89
|
case char.to_sym
|
77
90
|
when :'"'
|
78
|
-
consume_string
|
91
|
+
consume_string
|
79
92
|
|
80
93
|
when :'#'
|
81
|
-
if @s.peek =~ RE_NAME || valid_escape?
|
94
|
+
if @s.peek =~ RE_NAME || valid_escape?(@s.peek(2))
|
82
95
|
create_token(:hash,
|
83
|
-
:type => start_identifier? ? :id : :unrestricted,
|
96
|
+
:type => start_identifier?(@s.peek(3)) ? :id : :unrestricted,
|
84
97
|
:value => consume_name)
|
85
98
|
else
|
86
99
|
create_token(:delim, :value => char)
|
@@ -95,7 +108,7 @@ module Crass
|
|
95
108
|
end
|
96
109
|
|
97
110
|
when :"'"
|
98
|
-
consume_string
|
111
|
+
consume_string
|
99
112
|
|
100
113
|
when :'('
|
101
114
|
create_token(:'(')
|
@@ -108,8 +121,8 @@ module Crass
|
|
108
121
|
@s.consume
|
109
122
|
create_token(:substring_match)
|
110
123
|
|
124
|
+
# Non-standard: Preserve the IE * hack.
|
111
125
|
elsif @options[:preserve_hacks] && @s.peek =~ RE_NAME_START
|
112
|
-
# NON-STANDARD: IE * hack
|
113
126
|
@s.reconsume
|
114
127
|
consume_ident
|
115
128
|
|
@@ -118,7 +131,7 @@ module Crass
|
|
118
131
|
end
|
119
132
|
|
120
133
|
when :+
|
121
|
-
if start_number?
|
134
|
+
if start_number?
|
122
135
|
@s.reconsume
|
123
136
|
consume_numeric
|
124
137
|
else
|
@@ -129,47 +142,31 @@ module Crass
|
|
129
142
|
create_token(:comma)
|
130
143
|
|
131
144
|
when :-
|
132
|
-
|
145
|
+
nextTwoChars = @s.peek(2)
|
146
|
+
nextThreeChars = char + nextTwoChars
|
147
|
+
|
148
|
+
if start_number?(nextThreeChars)
|
133
149
|
@s.reconsume
|
134
150
|
consume_numeric
|
135
|
-
elsif
|
136
|
-
@s.reconsume
|
137
|
-
consume_ident
|
138
|
-
elsif @s.peek(2) == '->'
|
151
|
+
elsif nextTwoChars == '->'
|
139
152
|
@s.consume
|
140
153
|
@s.consume
|
141
154
|
create_token(:cdc)
|
155
|
+
elsif start_identifier?(nextThreeChars)
|
156
|
+
@s.reconsume
|
157
|
+
consume_ident
|
142
158
|
else
|
143
159
|
create_token(:delim, :value => char)
|
144
160
|
end
|
145
161
|
|
146
162
|
when :'.'
|
147
|
-
if start_number?
|
163
|
+
if start_number?
|
148
164
|
@s.reconsume
|
149
165
|
consume_numeric
|
150
166
|
else
|
151
167
|
create_token(:delim, :value => char)
|
152
168
|
end
|
153
169
|
|
154
|
-
when :/
|
155
|
-
if @s.peek == '*'
|
156
|
-
@s.consume
|
157
|
-
|
158
|
-
if text = @s.scan_until(RE_COMMENT_CLOSE)
|
159
|
-
text.slice!(-2, 2)
|
160
|
-
else
|
161
|
-
text = @s.consume_rest
|
162
|
-
end
|
163
|
-
|
164
|
-
if @options[:preserve_comments]
|
165
|
-
create_token(:comment, :value => text)
|
166
|
-
else
|
167
|
-
consume
|
168
|
-
end
|
169
|
-
else
|
170
|
-
create_token(:delim, :value => char)
|
171
|
-
end
|
172
|
-
|
173
170
|
when :':'
|
174
171
|
create_token(:colon)
|
175
172
|
|
@@ -188,7 +185,7 @@ module Crass
|
|
188
185
|
end
|
189
186
|
|
190
187
|
when :'@'
|
191
|
-
if start_identifier?
|
188
|
+
if start_identifier?(@s.peek(3))
|
192
189
|
create_token(:at_keyword, :value => consume_name)
|
193
190
|
else
|
194
191
|
create_token(:delim, :value => char)
|
@@ -198,10 +195,11 @@ module Crass
|
|
198
195
|
create_token(:'[')
|
199
196
|
|
200
197
|
when :'\\'
|
201
|
-
if valid_escape?
|
198
|
+
if valid_escape?
|
202
199
|
@s.reconsume
|
203
200
|
consume_ident
|
204
201
|
else
|
202
|
+
# Parse error.
|
205
203
|
create_token(:delim,
|
206
204
|
:error => true,
|
207
205
|
:value => char)
|
@@ -273,14 +271,14 @@ module Crass
|
|
273
271
|
|
274
272
|
# Consumes the remnants of a bad URL and returns the consumed text.
|
275
273
|
#
|
276
|
-
# http://
|
274
|
+
# 4.3.15. http://dev.w3.org/csswg/css-syntax/#consume-the-remnants-of-a-bad-url
|
277
275
|
def consume_bad_url
|
278
276
|
text = ''
|
279
277
|
|
280
278
|
until @s.eos?
|
281
|
-
if valid_escape?
|
279
|
+
if valid_escape?
|
282
280
|
text << consume_escaped
|
283
|
-
elsif valid_escape?
|
281
|
+
elsif valid_escape?(@s.peek(2))
|
284
282
|
@s.consume
|
285
283
|
text << consume_escaped
|
286
284
|
else
|
@@ -297,19 +295,38 @@ module Crass
|
|
297
295
|
text
|
298
296
|
end
|
299
297
|
|
298
|
+
# Consumes comments and returns them, or `nil` if no comments were consumed.
|
299
|
+
#
|
300
|
+
# 4.3.2. http://dev.w3.org/csswg/css-syntax/#consume-comments
|
301
|
+
def consume_comments
|
302
|
+
if @s.peek(2) == '/*'
|
303
|
+
@s.consume
|
304
|
+
@s.consume
|
305
|
+
|
306
|
+
if text = @s.scan_until(RE_COMMENT_CLOSE)
|
307
|
+
text.slice!(-2, 2)
|
308
|
+
else
|
309
|
+
# Parse error.
|
310
|
+
text = @s.consume_rest
|
311
|
+
end
|
312
|
+
|
313
|
+
return create_token(:comment, :value => text)
|
314
|
+
end
|
315
|
+
|
316
|
+
nil
|
317
|
+
end
|
318
|
+
|
300
319
|
# Consumes an escaped code point and returns its unescaped value.
|
301
320
|
#
|
302
321
|
# This method assumes that the `\` has already been consumed, and that the
|
303
322
|
# next character in the input has already been verified not to be a newline
|
304
323
|
# or EOF.
|
305
324
|
#
|
306
|
-
# http://
|
325
|
+
# 4.3.8. http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-code-point
|
307
326
|
def consume_escaped
|
308
|
-
|
309
|
-
when @s.eos?
|
310
|
-
"\ufffd"
|
327
|
+
return "\ufffd" if @s.eos?
|
311
328
|
|
312
|
-
|
329
|
+
if hex_str = @s.scan(RE_HEX)
|
313
330
|
@s.consume if @s.peek =~ RE_WHITESPACE
|
314
331
|
|
315
332
|
codepoint = hex_str.hex
|
@@ -318,19 +335,18 @@ module Crass
|
|
318
335
|
codepoint.between?(0xD800, 0xDFFF) ||
|
319
336
|
codepoint > 0x10FFFF
|
320
337
|
|
321
|
-
"\ufffd"
|
338
|
+
return "\ufffd"
|
322
339
|
else
|
323
|
-
codepoint.chr(Encoding::UTF_8)
|
340
|
+
return codepoint.chr(Encoding::UTF_8)
|
324
341
|
end
|
325
|
-
|
326
|
-
else
|
327
|
-
@s.consume
|
328
342
|
end
|
343
|
+
|
344
|
+
@s.consume
|
329
345
|
end
|
330
346
|
|
331
347
|
# Consumes an ident-like token and returns it.
|
332
348
|
#
|
333
|
-
# http://
|
349
|
+
# 4.3.4. http://dev.w3.org/csswg/css-syntax/#consume-an-ident-like-token
|
334
350
|
def consume_ident
|
335
351
|
value = consume_name
|
336
352
|
|
@@ -338,7 +354,13 @@ module Crass
|
|
338
354
|
@s.consume
|
339
355
|
|
340
356
|
if value.downcase == 'url'
|
341
|
-
|
357
|
+
@s.consume while @s.peek(2) =~ RE_WHITESPACE_ANCHORED
|
358
|
+
|
359
|
+
if @s.peek(2) =~ RE_QUOTED_URL_START
|
360
|
+
create_token(:function, :value => value)
|
361
|
+
else
|
362
|
+
consume_url
|
363
|
+
end
|
342
364
|
else
|
343
365
|
create_token(:function, :value => value)
|
344
366
|
end
|
@@ -349,37 +371,39 @@ module Crass
|
|
349
371
|
|
350
372
|
# Consumes a name and returns it.
|
351
373
|
#
|
352
|
-
# http://
|
374
|
+
# 4.3.12. http://dev.w3.org/csswg/css-syntax/#consume-a-name
|
353
375
|
def consume_name
|
354
376
|
result = ''
|
355
377
|
|
356
|
-
|
378
|
+
until @s.eos?
|
357
379
|
if match = @s.scan(RE_NAME)
|
358
380
|
result << match
|
359
381
|
next
|
360
382
|
end
|
361
383
|
|
362
|
-
char = @s.
|
384
|
+
char = @s.consume
|
363
385
|
|
364
|
-
if
|
365
|
-
@s.consume
|
386
|
+
if valid_escape?
|
366
387
|
result << consume_escaped
|
367
388
|
|
368
|
-
#
|
369
|
-
elsif
|
389
|
+
# Non-standard: IE * hack
|
390
|
+
elsif char == '*' && @options[:preserve_hacks]
|
370
391
|
result << @s.consume
|
371
392
|
|
372
393
|
else
|
394
|
+
@s.reconsume
|
373
395
|
return result
|
374
396
|
end
|
375
397
|
end
|
398
|
+
|
399
|
+
result
|
376
400
|
end
|
377
401
|
|
378
402
|
# Consumes a number and returns a 3-element array containing the number's
|
379
403
|
# original representation, its numeric value, and its type (either
|
380
404
|
# `:integer` or `:number`).
|
381
405
|
#
|
382
|
-
# http://
|
406
|
+
# 4.3.13. http://dev.w3.org/csswg/css-syntax/#consume-a-number
|
383
407
|
def consume_number
|
384
408
|
repr = ''
|
385
409
|
type = :integer
|
@@ -402,11 +426,11 @@ module Crass
|
|
402
426
|
|
403
427
|
# Consumes a numeric token and returns it.
|
404
428
|
#
|
405
|
-
# http://
|
429
|
+
# 4.3.3. http://dev.w3.org/csswg/css-syntax/#consume-a-numeric-token
|
406
430
|
def consume_numeric
|
407
431
|
number = consume_number
|
408
432
|
|
409
|
-
if start_identifier?
|
433
|
+
if start_identifier?(@s.peek(3))
|
410
434
|
create_token(:dimension,
|
411
435
|
:repr => number[0],
|
412
436
|
:type => number[2],
|
@@ -432,9 +456,10 @@ module Crass
|
|
432
456
|
# Consumes a string token that ends at the given character, and returns the
|
433
457
|
# token.
|
434
458
|
#
|
435
|
-
# http://
|
436
|
-
def consume_string(ending)
|
437
|
-
|
459
|
+
# 4.3.5. http://dev.w3.org/csswg/css-syntax/#consume-a-string-token
|
460
|
+
def consume_string(ending = nil)
|
461
|
+
ending = @s.current if ending.nil?
|
462
|
+
value = ''
|
438
463
|
|
439
464
|
until @s.eos?
|
440
465
|
case char = @s.consume
|
@@ -442,6 +467,7 @@ module Crass
|
|
442
467
|
break
|
443
468
|
|
444
469
|
when "\n"
|
470
|
+
# Parse error.
|
445
471
|
@s.reconsume
|
446
472
|
return create_token(:bad_string,
|
447
473
|
:error => true,
|
@@ -471,7 +497,7 @@ module Crass
|
|
471
497
|
# Consumes a Unicode range token and returns it. Assumes the initial "u+" or
|
472
498
|
# "U+" has already been consumed.
|
473
499
|
#
|
474
|
-
# http://
|
500
|
+
# 4.3.7. http://dev.w3.org/csswg/css-syntax/#consume-a-unicode-range-token
|
475
501
|
def consume_unicode_range
|
476
502
|
value = @s.scan(RE_HEX) || ''
|
477
503
|
|
@@ -503,71 +529,46 @@ module Crass
|
|
503
529
|
# Consumes a URL token and returns it. Assumes the original "url(" has
|
504
530
|
# already been consumed.
|
505
531
|
#
|
506
|
-
# http://
|
532
|
+
# 4.3.6. http://dev.w3.org/csswg/css-syntax/#consume-a-url-token
|
507
533
|
def consume_url
|
508
534
|
value = ''
|
509
535
|
|
510
536
|
@s.scan(RE_WHITESPACE)
|
511
537
|
|
512
|
-
if @s.eos?
|
513
|
-
return create_token(:url, :value => value)
|
514
|
-
end
|
515
|
-
|
516
|
-
# Quoted URL.
|
517
|
-
next_char = @s.peek
|
518
|
-
|
519
|
-
if next_char == "'" || next_char == '"'
|
520
|
-
string = consume_string(@s.consume)
|
521
|
-
|
522
|
-
if string[:node] == :bad_string
|
523
|
-
return create_token(:bad_url,
|
524
|
-
:value => string[:value] + consume_bad_url)
|
525
|
-
end
|
526
|
-
|
527
|
-
value = string[:value]
|
528
|
-
@s.scan(RE_WHITESPACE)
|
529
|
-
|
530
|
-
if @s.eos? || @s.peek == ')'
|
531
|
-
@s.consume
|
532
|
-
return create_token(:url, :value => value)
|
533
|
-
else
|
534
|
-
return create_token(:bad_url, :value => value + consume_bad_url)
|
535
|
-
end
|
536
|
-
end
|
537
|
-
|
538
|
-
# Unquoted URL.
|
539
538
|
until @s.eos?
|
540
539
|
case char = @s.consume
|
541
|
-
|
542
|
-
break
|
543
|
-
|
544
|
-
when RE_WHITESPACE
|
545
|
-
@s.scan(RE_WHITESPACE)
|
546
|
-
|
547
|
-
if @s.eos? || @s.peek == ')'
|
548
|
-
@s.consume
|
540
|
+
when ')'
|
549
541
|
break
|
550
|
-
else
|
551
|
-
return create_token(:bad_url, :value => value + consume_bad_url)
|
552
|
-
end
|
553
542
|
|
554
|
-
|
555
|
-
|
556
|
-
:error => true,
|
557
|
-
:value => value + consume_bad_url)
|
543
|
+
when RE_WHITESPACE
|
544
|
+
@s.scan(RE_WHITESPACE)
|
558
545
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
546
|
+
if @s.eos? || @s.peek == ')'
|
547
|
+
@s.consume
|
548
|
+
break
|
549
|
+
else
|
550
|
+
return create_token(:bad_url, :value => value + consume_bad_url)
|
551
|
+
end
|
552
|
+
|
553
|
+
when '"', "'", '(', RE_NON_PRINTABLE
|
554
|
+
# Parse error.
|
563
555
|
return create_token(:bad_url,
|
564
556
|
:error => true,
|
565
|
-
:value => value + consume_bad_url
|
566
|
-
|
567
|
-
|
557
|
+
:value => value + consume_bad_url)
|
558
|
+
|
559
|
+
when '\\'
|
560
|
+
if valid_escape?
|
561
|
+
value << consume_escaped
|
562
|
+
else
|
563
|
+
# Parse error.
|
564
|
+
return create_token(:bad_url,
|
565
|
+
:error => true,
|
566
|
+
:value => value + consume_bad_url
|
567
|
+
)
|
568
|
+
end
|
568
569
|
|
569
|
-
|
570
|
-
|
570
|
+
else
|
571
|
+
value << char
|
571
572
|
end
|
572
573
|
end
|
573
574
|
|
@@ -576,7 +577,7 @@ module Crass
|
|
576
577
|
|
577
578
|
# Converts a valid CSS number string into a number and returns the number.
|
578
579
|
#
|
579
|
-
# http://
|
580
|
+
# 4.3.14. http://dev.w3.org/csswg/css-syntax/#convert-a-string-to-a-number
|
580
581
|
def convert_string_to_number(str)
|
581
582
|
matches = RE_NUMBER_STR.match(str)
|
582
583
|
|
@@ -603,7 +604,7 @@ module Crass
|
|
603
604
|
|
604
605
|
# Preprocesses _input_ to prepare it for the tokenizer.
|
605
606
|
#
|
606
|
-
# http://
|
607
|
+
# 3.3. http://dev.w3.org/csswg/css-syntax/#input-preprocessing
|
607
608
|
def preprocess(input)
|
608
609
|
input = input.to_s.encode('UTF-8',
|
609
610
|
:invalid => :replace,
|
@@ -615,16 +616,17 @@ module Crass
|
|
615
616
|
end
|
616
617
|
|
617
618
|
# Returns `true` if the given three-character _text_ would start an
|
618
|
-
# identifier. If _text_ is `nil`, the next
|
619
|
-
# stream will be checked, but will not be consumed.
|
619
|
+
# identifier. If _text_ is `nil`, the current and next two characters in the
|
620
|
+
# input stream will be checked, but will not be consumed.
|
620
621
|
#
|
621
|
-
# http://
|
622
|
+
# 4.3.10. http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
|
622
623
|
def start_identifier?(text = nil)
|
623
|
-
text = @s.peek(
|
624
|
+
text = @s.current + @s.peek(2) if text.nil?
|
624
625
|
|
625
626
|
case text[0]
|
626
627
|
when '-'
|
627
|
-
|
628
|
+
nextChar = text[1]
|
629
|
+
!!(nextChar == '-' || nextChar =~ RE_NAME_START || valid_escape?(text[1, 2]))
|
628
630
|
|
629
631
|
when RE_NAME_START
|
630
632
|
true
|
@@ -638,12 +640,12 @@ module Crass
|
|
638
640
|
end
|
639
641
|
|
640
642
|
# Returns `true` if the given three-character _text_ would start a number.
|
641
|
-
# If _text_ is `nil`, the next
|
642
|
-
# checked, but will not be consumed.
|
643
|
+
# If _text_ is `nil`, the current and next two characters in the input
|
644
|
+
# stream will be checked, but will not be consumed.
|
643
645
|
#
|
644
|
-
# http://
|
646
|
+
# 4.3.11. http://dev.w3.org/csswg/css-syntax/#starts-with-a-number
|
645
647
|
def start_number?(text = nil)
|
646
|
-
text = @s.peek(
|
648
|
+
text = @s.current + @s.peek(2) if text.nil?
|
647
649
|
|
648
650
|
case text[0]
|
649
651
|
when '+', '-'
|
@@ -674,12 +676,12 @@ module Crass
|
|
674
676
|
end
|
675
677
|
|
676
678
|
# Returns `true` if the given two-character _text_ is the beginning of a
|
677
|
-
# valid escape sequence. If _text_ is `nil`, the
|
678
|
-
# input stream will be checked, but will not be consumed.
|
679
|
+
# valid escape sequence. If _text_ is `nil`, the current and next character
|
680
|
+
# in the input stream will be checked, but will not be consumed.
|
679
681
|
#
|
680
|
-
# http://
|
682
|
+
# 4.3.9. http://dev.w3.org/csswg/css-syntax/#starts-with-a-valid-escape
|
681
683
|
def valid_escape?(text = nil)
|
682
|
-
text = @s.peek
|
684
|
+
text = @s.current + @s.peek if text.nil?
|
683
685
|
!!(text[0] == '\\' && text[1] != "\n")
|
684
686
|
end
|
685
687
|
end
|