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
@@ -0,0 +1,268 @@
1
+ /*! HTML5 Boilerplate v4.3.0 | MIT License | http://h5bp.com/ */
2
+
3
+ /*
4
+ * What follows is the result of much research on cross-browser styling.
5
+ * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
6
+ * Kroc Camen, and the H5BP dev community and team.
7
+ */
8
+
9
+ /* ==========================================================================
10
+ Base styles: opinionated defaults
11
+ ========================================================================== */
12
+
13
+ html {
14
+ color: #222;
15
+ font-size: 1em;
16
+ line-height: 1.4;
17
+ }
18
+
19
+ /*
20
+ * Remove text-shadow in selection highlight: h5bp.com/i
21
+ * These selection rule sets have to be separate.
22
+ * Customize the background color to match your design.
23
+ */
24
+
25
+ ::-moz-selection {
26
+ background: #b3d4fc;
27
+ text-shadow: none;
28
+ }
29
+
30
+ ::selection {
31
+ background: #b3d4fc;
32
+ text-shadow: none;
33
+ }
34
+
35
+ /*
36
+ * A better looking default horizontal rule
37
+ */
38
+
39
+ hr {
40
+ display: block;
41
+ height: 1px;
42
+ border: 0;
43
+ border-top: 1px solid #ccc;
44
+ margin: 1em 0;
45
+ padding: 0;
46
+ }
47
+
48
+ /*
49
+ * Remove the gap between images, videos, audio and canvas and the bottom of
50
+ * their containers: h5bp.com/i/440
51
+ */
52
+
53
+ audio,
54
+ canvas,
55
+ img,
56
+ svg,
57
+ video {
58
+ vertical-align: middle;
59
+ }
60
+
61
+ /*
62
+ * Remove default fieldset styles.
63
+ */
64
+
65
+ fieldset {
66
+ border: 0;
67
+ margin: 0;
68
+ padding: 0;
69
+ }
70
+
71
+ /*
72
+ * Allow only vertical resizing of textareas.
73
+ */
74
+
75
+ textarea {
76
+ resize: vertical;
77
+ }
78
+
79
+ /* ==========================================================================
80
+ Browser Upgrade Prompt
81
+ ========================================================================== */
82
+
83
+ .browserupgrade {
84
+ margin: 0.2em 0;
85
+ background: #ccc;
86
+ color: #000;
87
+ padding: 0.2em 0;
88
+ }
89
+
90
+ /* ==========================================================================
91
+ Author's custom styles
92
+ ========================================================================== */
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+
104
+
105
+
106
+
107
+
108
+
109
+
110
+ /* ==========================================================================
111
+ Helper classes
112
+ ========================================================================== */
113
+
114
+ /*
115
+ * Hide visually and from screen readers: h5bp.com/u
116
+ */
117
+
118
+ .hidden {
119
+ display: none !important;
120
+ visibility: hidden;
121
+ }
122
+
123
+ /*
124
+ * Hide only visually, but have it available for screen readers: h5bp.com/v
125
+ */
126
+
127
+ .visuallyhidden {
128
+ border: 0;
129
+ clip: rect(0 0 0 0);
130
+ height: 1px;
131
+ margin: -1px;
132
+ overflow: hidden;
133
+ padding: 0;
134
+ position: absolute;
135
+ width: 1px;
136
+ }
137
+
138
+ /*
139
+ * Extends the .visuallyhidden class to allow the element to be focusable
140
+ * when navigated to via the keyboard: h5bp.com/p
141
+ */
142
+
143
+ .visuallyhidden.focusable:active,
144
+ .visuallyhidden.focusable:focus {
145
+ clip: auto;
146
+ height: auto;
147
+ margin: 0;
148
+ overflow: visible;
149
+ position: static;
150
+ width: auto;
151
+ }
152
+
153
+ /*
154
+ * Hide visually and from screen readers, but maintain layout
155
+ */
156
+
157
+ .invisible {
158
+ visibility: hidden;
159
+ }
160
+
161
+ /*
162
+ * Clearfix: contain floats
163
+ *
164
+ * For modern browsers
165
+ * 1. The space content is one way to avoid an Opera bug when the
166
+ * `contenteditable` attribute is included anywhere else in the document.
167
+ * Otherwise it causes space to appear at the top and bottom of elements
168
+ * that receive the `clearfix` class.
169
+ * 2. The use of `table` rather than `block` is only necessary if using
170
+ * `:before` to contain the top-margins of child elements.
171
+ */
172
+
173
+ .clearfix:before,
174
+ .clearfix:after {
175
+ content: " "; /* 1 */
176
+ display: table; /* 2 */
177
+ }
178
+
179
+ .clearfix:after {
180
+ clear: both;
181
+ }
182
+
183
+ /* ==========================================================================
184
+ EXAMPLE Media Queries for Responsive Design.
185
+ These examples override the primary ('mobile first') styles.
186
+ Modify as content requires.
187
+ ========================================================================== */
188
+
189
+ @media only screen and (min-width: 35em) {
190
+ /* Style adjustments for viewports that meet the condition */
191
+ }
192
+
193
+ @media print,
194
+ (-o-min-device-pixel-ratio: 5/4),
195
+ (-webkit-min-device-pixel-ratio: 1.25),
196
+ (min-resolution: 120dpi) {
197
+ /* Style adjustments for high resolution devices */
198
+ }
199
+
200
+ /* ==========================================================================
201
+ Print styles.
202
+ Inlined to avoid the additional HTTP request: h5bp.com/r
203
+ ========================================================================== */
204
+
205
+ @media print {
206
+ *,
207
+ *:before,
208
+ *:after {
209
+ background: transparent !important;
210
+ color: #000 !important; /* Black prints faster: h5bp.com/s */
211
+ box-shadow: none !important;
212
+ text-shadow: none !important;
213
+ }
214
+
215
+ a,
216
+ a:visited {
217
+ text-decoration: underline;
218
+ }
219
+
220
+ a[href]:after {
221
+ content: " (" attr(href) ")";
222
+ }
223
+
224
+ abbr[title]:after {
225
+ content: " (" attr(title) ")";
226
+ }
227
+
228
+ /*
229
+ * Don't show links that are fragment identifiers,
230
+ * or use the `javascript:` pseudo protocol
231
+ */
232
+
233
+ a[href^="#"]:after,
234
+ a[href^="javascript:"]:after {
235
+ content: "";
236
+ }
237
+
238
+ pre,
239
+ blockquote {
240
+ border: 1px solid #999;
241
+ page-break-inside: avoid;
242
+ }
243
+
244
+ thead {
245
+ display: table-header-group; /* h5bp.com/t */
246
+ }
247
+
248
+ tr,
249
+ img {
250
+ page-break-inside: avoid;
251
+ }
252
+
253
+ img {
254
+ max-width: 100% !important;
255
+ }
256
+
257
+ p,
258
+ h2,
259
+ h3 {
260
+ orphans: 3;
261
+ widows: 3;
262
+ }
263
+
264
+ h2,
265
+ h3 {
266
+ page-break-after: avoid;
267
+ }
268
+ }
@@ -0,0 +1,9 @@
1
+ /* mangled !important */
2
+ .foo {
3
+ display: none /**/ ! IMPORTANT /* */ ;
4
+ }
5
+
6
+ /* An+B */
7
+ li:nth-child(even of li:not(.filtered)) {
8
+ background-color: black;
9
+ }
@@ -0,0 +1,150 @@
1
+ # encoding: utf-8
2
+
3
+ # This file loads and runs Simon Sapin's CSS parsing tests, which live under the
4
+ # test/css-parsing-tests directory. The original test repo can be found at:
5
+ #
6
+ # https://github.com/SimonSapin/css-parsing-tests/
7
+
8
+ require 'json'
9
+ require_relative 'support/common'
10
+
11
+ def load_css_tests(filename)
12
+ JSON.parse(File.read(File.join(File.dirname(__FILE__), "/css-parsing-tests/#{filename}")))
13
+ end
14
+
15
+ describe 'CSS Parsing Tests' do
16
+ describe 'component_value_list' do
17
+ make_my_diffs_pretty!
18
+ parallelize_me!
19
+
20
+ tests = load_css_tests('component_value_list.json')
21
+
22
+ tests.each_slice(2) do |test|
23
+ css = test[0]
24
+ expected = test[1]
25
+
26
+ it "should parse: #{css.gsub("\n", "\\n")}" do
27
+ parser = Crass::Parser.new(css)
28
+ assert_equal(expected, translate_tokens(parser.parse_component_values))
29
+ end
30
+ end
31
+ end
32
+
33
+ describe 'declaration_list' do
34
+ make_my_diffs_pretty!
35
+ parallelize_me!
36
+
37
+ tests = load_css_tests('declaration_list.json')
38
+
39
+ tests.each_slice(2) do |test|
40
+ css = test[0]
41
+ expected = test[1]
42
+
43
+ it "should parse: #{css.gsub("\n", "\\n")}" do
44
+ parser = Crass::Parser.new(css)
45
+ assert_equal(expected, translate_tokens(parser.parse_declarations(parser.tokens, {:strict => true})))
46
+ end
47
+ end
48
+ end
49
+
50
+ describe 'one_component_value' do
51
+ make_my_diffs_pretty!
52
+ parallelize_me!
53
+
54
+ tests = load_css_tests('one_component_value.json')
55
+
56
+ tests.each_slice(2) do |test|
57
+ css = test[0]
58
+ expected = test[1]
59
+
60
+ it "should parse: #{css.gsub("\n", "\\n")}" do
61
+ parser = Crass::Parser.new(css)
62
+ assert_equal(expected, translate_tokens(parser.parse_component_value)[0])
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'one_declaration' do
68
+ make_my_diffs_pretty!
69
+ parallelize_me!
70
+
71
+ tests = load_css_tests('one_declaration.json')
72
+
73
+ tests.each_slice(2) do |test|
74
+ css = test[0]
75
+ expected = test[1]
76
+
77
+ it "should parse: #{css.gsub("\n", "\\n")}" do
78
+ parser = Crass::Parser.new(css)
79
+ assert_equal(expected, translate_tokens(parser.parse_declaration)[0])
80
+ end
81
+ end
82
+ end
83
+
84
+ describe 'one_rule' do
85
+ make_my_diffs_pretty!
86
+ parallelize_me!
87
+
88
+ tests = load_css_tests('one_rule.json')
89
+
90
+ tests.each_slice(2) do |test|
91
+ css = test[0]
92
+ expected = test[1]
93
+
94
+ it "should parse: #{css.gsub("\n", "\\n")}" do
95
+ parser = Crass::Parser.new(css)
96
+ assert_equal(expected, translate_tokens(parser.parse_rule)[0])
97
+ end
98
+ end
99
+ end
100
+
101
+ describe 'rule_list' do
102
+ make_my_diffs_pretty!
103
+ parallelize_me!
104
+
105
+ tests = load_css_tests('rule_list.json')
106
+
107
+ tests.each_slice(2) do |test|
108
+ css = test[0]
109
+ expected = test[1]
110
+
111
+ it "should parse: #{css.gsub("\n", "\\n")}" do
112
+ parser = Crass::Parser.new(css)
113
+ rules = parser.consume_rules
114
+
115
+ # Remove non-standard whitespace tokens.
116
+ rules.reject! do |token|
117
+ node = token[:node]
118
+ node == :whitespace
119
+ end
120
+
121
+ assert_equal(expected, translate_tokens(rules))
122
+ end
123
+ end
124
+ end
125
+
126
+ describe 'stylesheet' do
127
+ make_my_diffs_pretty!
128
+ parallelize_me!
129
+
130
+ tests = load_css_tests('stylesheet.json')
131
+
132
+ tests.each_slice(2) do |test|
133
+ css = test[0]
134
+ expected = test[1]
135
+
136
+ it "should parse: #{css.gsub("\n", "\\n")}" do
137
+ parser = Crass::Parser.new(css)
138
+ rules = parser.consume_rules(:top_level => true)
139
+
140
+ # Remove non-standard whitespace tokens.
141
+ rules.reject! do |token|
142
+ node = token[:node]
143
+ node == :whitespace
144
+ end
145
+
146
+ assert_equal(expected, translate_tokens(rules))
147
+ end
148
+ end
149
+ end
150
+ end
@@ -29,169 +29,42 @@ describe 'Crass::Parser' do
29
29
  assert_tokens(";; /**/ ; ;", tree, 0, :preserve_comments => true)
30
30
  end
31
31
 
32
- it 'should parse a list of declarations' do
33
- tree = parse("a:b; c:d 42!important;\n")
34
- assert_equal(4, tree.size)
35
-
36
- prop = tree[0]
37
- assert_equal(:property, prop[:node])
38
- assert_equal("a", prop[:name])
39
- assert_equal("b", prop[:value])
40
- assert_equal(false, prop[:important])
41
- assert_tokens("a:b;", prop[:tokens])
42
-
43
- assert_equal([
44
- {:node=>:ident, :pos=>2, :raw=>"b", :value=>"b"}
45
- ], prop[:children])
46
-
47
- assert_tokens(" ", tree[1], 4)
48
-
49
- prop = tree[2]
50
- assert_equal(:property, prop[:node])
51
- assert_equal("c", prop[:name])
52
- assert_equal("d 42", prop[:value])
53
- assert_equal(true, prop[:important])
54
- assert_tokens("c:d 42!important;", prop[:tokens], 5)
55
-
56
- assert_equal([
57
- {:node=>:ident, :pos=>7, :raw=>"d", :value=>"d"},
58
- {:node=>:whitespace, :pos=>8, :raw=>" "},
59
- {:node=>:number,
60
- :pos=>9,
61
- :raw=>"42",
62
- :repr=>"42",
63
- :type=>:integer,
64
- :value=>42}
65
- ], prop[:children])
66
-
67
- assert_tokens("\n", tree[3], 22)
68
- end
69
-
70
32
  it 'should parse at-rules even though they may be invalid in the given context' do
71
33
  tree = parse("@import 'foo.css'; a:b; @import 'bar.css'")
72
- assert_equal(5, tree.size)
73
-
74
- rule = tree[0]
75
- assert_equal(:at_rule, rule[:node])
76
- assert_equal("import", rule[:name])
77
- assert_tokens(" 'foo.css'", rule[:prelude], 7)
78
- assert_tokens("@import 'foo.css';", rule[:tokens])
79
-
80
- assert_tokens(" ", tree[1], 18)
81
-
82
- prop = tree[2]
83
- assert_equal(:property, prop[:node])
84
- assert_equal("a", prop[:name])
85
- assert_equal("b", prop[:value])
86
- assert_equal(false, prop[:important])
87
- assert_tokens("a:b;", prop[:tokens], 19)
88
34
 
89
35
  assert_equal([
90
- {:node=>:ident, :pos=>21, :raw=>"b", :value=>"b"}
91
- ], prop[:children])
92
-
93
- assert_tokens(" ", tree[3], 23)
94
-
95
- rule = tree[4]
96
- assert_equal(:at_rule, rule[:node])
97
- assert_equal("import", rule[:name])
98
- assert_tokens(" 'bar.css'", rule[:prelude], 31)
99
- assert_tokens("@import 'bar.css'", rule[:tokens], 24)
100
- end
101
-
102
- it 'should not be fazed by extra semicolons or unclosed blocks' do
103
- tree = parse("@media screen { div{;}} a:b;; @media print{div{")
104
- assert_equal(6, tree.size)
105
-
106
- rule = tree[0]
107
- assert_equal(:at_rule, rule[:node])
108
- assert_equal("media", rule[:name])
109
- assert_tokens(" screen ", rule[:prelude], 6)
110
- assert_tokens("@media screen { div{;}}", rule[:tokens])
111
-
112
- block = rule[:block]
113
- assert_equal(:simple_block, block[:node])
114
- assert_equal("{", block[:start])
115
- assert_equal("}", block[:end])
116
- assert_tokens("{ div{;}}", block[:tokens], 14)
117
-
118
- value = block[:value]
119
- assert_equal(3, value.size)
120
- assert_tokens(" div", value[0..1], 15)
121
-
122
- block = value[2]
123
- assert_equal(:simple_block, block[:node])
124
- assert_equal("{", block[:start])
125
- assert_equal("}", block[:end])
126
- assert_tokens(";", block[:value], 20)
127
- assert_tokens("{;}", block[:tokens], 19)
128
-
129
- assert_tokens(" ", tree[1], 23)
130
-
131
- prop = tree[2]
132
- assert_equal(:property, prop[:node])
133
- assert_equal("a", prop[:name])
134
- assert_equal("b", prop[:value])
135
- assert_equal(false, prop[:important])
136
- assert_tokens("a:b;", prop[:tokens], 24)
137
- assert_equal([
138
- {:node=>:ident, :pos=>26, :raw=>"b", :value=>"b"}
139
- ], prop[:children])
140
-
141
- assert_tokens("; ", tree[3..4], 28)
142
-
143
- rule = tree[5]
144
- assert_equal(:at_rule, rule[:node])
145
- assert_equal("media", rule[:name])
146
- assert_tokens(" print", rule[:prelude], 36)
147
- assert_tokens("@media print{div{", rule[:tokens], 30)
148
-
149
- block = rule[:block]
150
- assert_equal(:simple_block, block[:node])
151
- assert_equal("{", block[:start])
152
- assert_equal("}", block[:end])
153
- assert_tokens("{div{", block[:tokens], 42)
154
-
155
- value = block[:value]
156
- assert_equal(2, value.size)
157
- assert_tokens("div", value[0], 43)
158
-
159
- block = value[1]
160
- assert_equal(:simple_block, block[:node])
161
- assert_equal("{", block[:start])
162
- assert_equal("}", block[:end])
163
- assert_equal([], block[:value])
164
- assert_tokens("{", block[:tokens], 46)
165
- end
166
-
167
- it 'should discard invalid nodes' do
168
- tree = parse("@ media screen { div{;}} a:b;; @media print{div{")
169
- assert_equal(3, tree.size)
170
-
171
- assert_tokens("; ", tree[0..1], 29)
172
-
173
- rule = tree[2]
174
- assert_equal(:at_rule, rule[:node])
175
- assert_equal("media", rule[:name])
176
- assert_tokens(" print", rule[:prelude], 37)
177
- assert_tokens("@media print{div{", rule[:tokens], 31)
178
-
179
- block = rule[:block]
180
- assert_equal(:simple_block, block[:node])
181
- assert_equal("{", block[:start])
182
- assert_equal("}", block[:end])
183
- assert_tokens("{div{", block[:tokens], 43)
184
-
185
- value = block[:value]
186
- assert_equal(2, value.size)
187
- assert_tokens("div", value[0], 44)
188
-
189
- block = value[1]
190
- assert_equal(:simple_block, block[:node])
191
- assert_equal("{", block[:start])
192
- assert_equal("}", block[:end])
193
- assert_equal([], block[:value])
194
- assert_tokens("{", block[:tokens], 47)
36
+ {:node=>:at_rule,
37
+ :name=>"import",
38
+ :prelude=>
39
+ [{:node=>:whitespace, :pos=>7, :raw=>" "},
40
+ {:node=>:string, :pos=>8, :raw=>"'foo.css'", :value=>"foo.css"}],
41
+ :tokens=>
42
+ [{:node=>:at_keyword, :pos=>0, :raw=>"@import", :value=>"import"},
43
+ {:node=>:whitespace, :pos=>7, :raw=>" "},
44
+ {:node=>:string, :pos=>8, :raw=>"'foo.css'", :value=>"foo.css"},
45
+ {:node=>:semicolon, :pos=>17, :raw=>";"}]},
46
+ {:node=>:whitespace, :pos=>18, :raw=>" "},
47
+ {:node=>:property,
48
+ :name=>"a",
49
+ :value=>"b",
50
+ :children=>[{:node=>:ident, :pos=>21, :raw=>"b", :value=>"b"}],
51
+ :important=>false,
52
+ :tokens=>
53
+ [{:node=>:ident, :pos=>19, :raw=>"a", :value=>"a"},
54
+ {:node=>:colon, :pos=>20, :raw=>":"},
55
+ {:node=>:ident, :pos=>21, :raw=>"b", :value=>"b"}]},
56
+ {:node=>:semicolon, :pos=>22, :raw=>";"},
57
+ {:node=>:whitespace, :pos=>23, :raw=>" "},
58
+ {:node=>:at_rule,
59
+ :name=>"import",
60
+ :prelude=>
61
+ [{:node=>:whitespace, :pos=>31, :raw=>" "},
62
+ {:node=>:string, :pos=>32, :raw=>"'bar.css'", :value=>"bar.css"}],
63
+ :tokens=>
64
+ [{:node=>:at_keyword, :pos=>24, :raw=>"@import", :value=>"import"},
65
+ {:node=>:whitespace, :pos=>31, :raw=>" "},
66
+ {:node=>:string, :pos=>32, :raw=>"'bar.css'", :value=>"bar.css"}]}
67
+ ], tree)
195
68
  end
196
69
 
197
70
  it 'should parse values containing functions' do
@@ -199,27 +72,35 @@ describe 'Crass::Parser' do
199
72
 
200
73
  assert_equal([
201
74
  {:node=>:property,
202
- :name=>"content",
203
- :value=>"attr(data-foo) \" \"",
204
- :important=>false,
205
- :children=>
206
- [{:node=>:whitespace, :pos=>8, :raw=>" "},
207
- {:node=>:function, :pos=>9, :raw=>"attr(", :value=>"attr"},
208
- {:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"},
209
- {:node=>:")", :pos=>22, :raw=>")"},
210
- {:node=>:whitespace, :pos=>23, :raw=>" "},
211
- {:node=>:string, :pos=>24, :raw=>"\" \"", :value=>" "}],
212
- :tokens=>
213
- [{:node=>:ident, :pos=>0, :raw=>"content", :value=>"content"},
214
- {:node=>:colon, :pos=>7, :raw=>":"},
215
- {:node=>:whitespace, :pos=>8, :raw=>" "},
216
- {:node=>:function, :pos=>9, :raw=>"attr(", :value=>"attr"},
217
- {:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"},
218
- {:node=>:")", :pos=>22, :raw=>")"},
219
- {:node=>:whitespace, :pos=>23, :raw=>" "},
220
- {:node=>:string, :pos=>24, :raw=>"\" \"", :value=>" "},
221
- {:node=>:semicolon, :pos=>27, :raw=>";"}]}
222
- ], tree)
75
+ :name=>"content",
76
+ :value=>"attr(data-foo) \" \"",
77
+ :children=>
78
+ [{:node=>:whitespace, :pos=>8, :raw=>" "},
79
+ {:node=>:function,
80
+ :name=>"attr",
81
+ :value=>[{:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"}],
82
+ :tokens=>
83
+ [{:node=>:function, :pos=>9, :raw=>"attr(", :value=>"attr"},
84
+ {:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"},
85
+ {:node=>:")", :pos=>22, :raw=>")"}]},
86
+ {:node=>:whitespace, :pos=>23, :raw=>" "},
87
+ {:node=>:string, :pos=>24, :raw=>"\" \"", :value=>" "}],
88
+ :important=>false,
89
+ :tokens=>
90
+ [{:node=>:ident, :pos=>0, :raw=>"content", :value=>"content"},
91
+ {:node=>:colon, :pos=>7, :raw=>":"},
92
+ {:node=>:whitespace, :pos=>8, :raw=>" "},
93
+ {:node=>:function,
94
+ :name=>"attr",
95
+ :value=>[{:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"}],
96
+ :tokens=>
97
+ [{:node=>:function, :pos=>9, :raw=>"attr(", :value=>"attr"},
98
+ {:node=>:ident, :pos=>14, :raw=>"data-foo", :value=>"data-foo"},
99
+ {:node=>:")", :pos=>22, :raw=>")"}]},
100
+ {:node=>:whitespace, :pos=>23, :raw=>" "},
101
+ {:node=>:string, :pos=>24, :raw=>"\" \"", :value=>" "}]},
102
+ {:node=>:semicolon, :pos=>27, :raw=>";"}
103
+ ], tree)
223
104
  end
224
105
 
225
106
  it 'should parse values containing nested functions' do
@@ -227,37 +108,81 @@ describe 'Crass::Parser' do
227
108
 
228
109
  assert_equal([
229
110
  {:node=>:property,
230
- :name=>"width",
231
- :value=>"expression(alert(1))",
232
- :important=>false,
233
- :children=>
234
- [{:node=>:whitespace, :pos=>6, :raw=>" "},
235
- {:node=>:function, :pos=>7, :raw=>"expression(", :value=>"expression"},
236
- {:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
237
- {:node=>:number,
238
- :pos=>24,
239
- :raw=>"1",
240
- :repr=>"1",
241
- :type=>:integer,
242
- :value=>1},
243
- {:node=>:")", :pos=>25, :raw=>")"},
244
- {:node=>:")", :pos=>26, :raw=>")"}],
245
- :tokens=>
246
- [{:node=>:ident, :pos=>0, :raw=>"width", :value=>"width"},
247
- {:node=>:colon, :pos=>5, :raw=>":"},
248
- {:node=>:whitespace, :pos=>6, :raw=>" "},
249
- {:node=>:function, :pos=>7, :raw=>"expression(", :value=>"expression"},
250
- {:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
251
- {:node=>:number,
252
- :pos=>24,
253
- :raw=>"1",
254
- :repr=>"1",
255
- :type=>:integer,
256
- :value=>1},
257
- {:node=>:")", :pos=>25, :raw=>")"},
258
- {:node=>:")", :pos=>26, :raw=>")"},
259
- {:node=>:semicolon, :pos=>27, :raw=>";"}]}
260
- ], tree)
111
+ :name=>"width",
112
+ :value=>"expression(alert(1))",
113
+ :children=>
114
+ [{:node=>:whitespace, :pos=>6, :raw=>" "},
115
+ {:node=>:function,
116
+ :name=>"expression",
117
+ :value=>
118
+ [{:node=>:function,
119
+ :name=>"alert",
120
+ :value=>
121
+ [{:node=>:number,
122
+ :pos=>24,
123
+ :raw=>"1",
124
+ :repr=>"1",
125
+ :type=>:integer,
126
+ :value=>1}],
127
+ :tokens=>
128
+ [{:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
129
+ {:node=>:number,
130
+ :pos=>24,
131
+ :raw=>"1",
132
+ :repr=>"1",
133
+ :type=>:integer,
134
+ :value=>1},
135
+ {:node=>:")", :pos=>25, :raw=>")"}]}],
136
+ :tokens=>
137
+ [{:node=>:function, :pos=>7, :raw=>"expression(", :value=>"expression"},
138
+ {:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
139
+ {:node=>:number,
140
+ :pos=>24,
141
+ :raw=>"1",
142
+ :repr=>"1",
143
+ :type=>:integer,
144
+ :value=>1},
145
+ {:node=>:")", :pos=>25, :raw=>")"},
146
+ {:node=>:")", :pos=>26, :raw=>")"}]}],
147
+ :important=>false,
148
+ :tokens=>
149
+ [{:node=>:ident, :pos=>0, :raw=>"width", :value=>"width"},
150
+ {:node=>:colon, :pos=>5, :raw=>":"},
151
+ {:node=>:whitespace, :pos=>6, :raw=>" "},
152
+ {:node=>:function,
153
+ :name=>"expression",
154
+ :value=>
155
+ [{:node=>:function,
156
+ :name=>"alert",
157
+ :value=>
158
+ [{:node=>:number,
159
+ :pos=>24,
160
+ :raw=>"1",
161
+ :repr=>"1",
162
+ :type=>:integer,
163
+ :value=>1}],
164
+ :tokens=>
165
+ [{:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
166
+ {:node=>:number,
167
+ :pos=>24,
168
+ :raw=>"1",
169
+ :repr=>"1",
170
+ :type=>:integer,
171
+ :value=>1},
172
+ {:node=>:")", :pos=>25, :raw=>")"}]}],
173
+ :tokens=>
174
+ [{:node=>:function, :pos=>7, :raw=>"expression(", :value=>"expression"},
175
+ {:node=>:function, :pos=>18, :raw=>"alert(", :value=>"alert"},
176
+ {:node=>:number,
177
+ :pos=>24,
178
+ :raw=>"1",
179
+ :repr=>"1",
180
+ :type=>:integer,
181
+ :value=>1},
182
+ {:node=>:")", :pos=>25, :raw=>")"},
183
+ {:node=>:")", :pos=>26, :raw=>")"}]}]},
184
+ {:node=>:semicolon, :pos=>27, :raw=>";"}
185
+ ], tree)
261
186
  end
262
187
 
263
188
  it 'should not choke on a missing property value' do