crass 0.2.1 → 1.0.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 (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