crass 0.2.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/HISTORY.md +22 -1
  4. data/LICENSE +1 -1
  5. data/README.md +64 -72
  6. data/Rakefile +4 -0
  7. data/crass.gemspec +2 -2
  8. data/lib/crass.rb +1 -1
  9. data/lib/crass/parser.rb +231 -96
  10. data/lib/crass/scanner.rb +21 -21
  11. data/lib/crass/token-scanner.rb +8 -1
  12. data/lib/crass/tokenizer.rb +133 -131
  13. data/lib/crass/version.rb +1 -1
  14. data/test/css-parsing-tests/An+B.json +156 -0
  15. data/test/css-parsing-tests/LICENSE +8 -0
  16. data/test/css-parsing-tests/README.rst +301 -0
  17. data/test/css-parsing-tests/color3.json +142 -0
  18. data/test/css-parsing-tests/color3_hsl.json +3890 -0
  19. data/test/css-parsing-tests/color3_keywords.json +803 -0
  20. data/test/css-parsing-tests/component_value_list.json +432 -0
  21. data/test/css-parsing-tests/declaration_list.json +44 -0
  22. data/test/css-parsing-tests/make_color3_hsl.py +17 -0
  23. data/test/css-parsing-tests/make_color3_keywords.py +191 -0
  24. data/test/css-parsing-tests/one_component_value.json +27 -0
  25. data/test/css-parsing-tests/one_declaration.json +46 -0
  26. data/test/css-parsing-tests/one_rule.json +36 -0
  27. data/test/css-parsing-tests/rule_list.json +48 -0
  28. data/test/css-parsing-tests/stylesheet.json +44 -0
  29. data/test/css-parsing-tests/stylesheet_bytes.json +146 -0
  30. data/test/shared/parse_rules.rb +377 -434
  31. data/test/support/common.rb +124 -0
  32. data/test/support/serialization/animate.css +3158 -0
  33. data/test/support/serialization/html5-boilerplate.css +268 -0
  34. data/test/support/serialization/misc.css +9 -0
  35. data/test/test_css_parsing_tests.rb +150 -0
  36. data/test/test_parse_properties.rb +136 -211
  37. data/test/test_parse_rules.rb +0 -52
  38. data/test/test_parse_stylesheet.rb +0 -39
  39. data/test/test_serialization.rb +13 -4
  40. metadata +44 -7
  41. 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 = input.is_a?(IO) ? input.read : input.to_s
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 == @len
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
- @pos = @len
47
- @scanner.rest
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 = @chars[@marker, @pos - @marker]
66
- result.join('')
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
- @scanner.peek(length)
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 = @chars.size
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 = @chars[@pos - 1]
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 = @chars[@pos - 1]
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
@@ -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
@@ -5,7 +5,7 @@ module Crass
5
5
 
6
6
  # Tokenizes a CSS string.
7
7
  #
8
- # http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#tokenization
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-token0
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?(char + @s.peek(2))
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
- if start_number?(char + @s.peek(2))
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 start_identifier?(char + @s.peek(2))
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?(char + @s.peek(2))
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?(char + @s.peek)
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-the-remnants-of-a-bad-url
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?(@s.current + @s.peek)
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-escaped-code-point0
325
+ # 4.3.8. http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-code-point
307
326
  def consume_escaped
308
- case
309
- when @s.eos?
310
- "\ufffd"
327
+ return "\ufffd" if @s.eos?
311
328
 
312
- when hex_str = @s.scan(RE_HEX)
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-ident-like-token
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
- consume_url
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-name
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
- while true
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.peek
384
+ char = @s.consume
363
385
 
364
- if char == '\\' && valid_escape?
365
- @s.consume
386
+ if valid_escape?
366
387
  result << consume_escaped
367
388
 
368
- # NON-STANDARD: IE * hack
369
- elsif @options[:preserve_hacks] && char == '*'
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-number0
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-numeric-token
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-string-token
436
- def consume_string(ending)
437
- value = ''
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-unicode-range-token
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-url-token
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
- when ')'
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
- when '"', "'", '(', RE_NON_PRINTABLE
555
- return create_token(:bad_url,
556
- :error => true,
557
- :value => value + consume_bad_url)
543
+ when RE_WHITESPACE
544
+ @s.scan(RE_WHITESPACE)
558
545
 
559
- when '\\'
560
- if valid_escape?(char + @s.peek)
561
- value << consume_escaped
562
- else
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
- end
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
- else
570
- value << char
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#convert-a-string-to-a-number0
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#input-preprocessing
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 three characters in the input
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#check-if-three-code-points-would-start-an-identifier
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(3) if text.nil?
624
+ text = @s.current + @s.peek(2) if text.nil?
624
625
 
625
626
  case text[0]
626
627
  when '-'
627
- !!(text[1] =~ RE_NAME_START || valid_escape?(text[1, 2]))
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 three characters in the input stream will be
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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#check-if-three-code-points-would-start-a-number
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(3) if text.nil?
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 next two characters in 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://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#check-if-two-code-points-are-a-valid-escape
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(2) if text.nil?
684
+ text = @s.current + @s.peek if text.nil?
683
685
  !!(text[0] == '\\' && text[1] != "\n")
684
686
  end
685
687
  end