csspool-st 3.1.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.
- data/.autotest +16 -0
- data/.gemtest +0 -0
- data/CHANGELOG.rdoc +87 -0
- data/Manifest.txt +62 -0
- data/README.rdoc +65 -0
- data/Rakefile +50 -0
- data/lib/csspool/collection.rb +50 -0
- data/lib/csspool/css/charset.rb +13 -0
- data/lib/csspool/css/declaration.rb +19 -0
- data/lib/csspool/css/document.rb +34 -0
- data/lib/csspool/css/document_handler.rb +51 -0
- data/lib/csspool/css/import_rule.rb +27 -0
- data/lib/csspool/css/media.rb +13 -0
- data/lib/csspool/css/parser.rb +1298 -0
- data/lib/csspool/css/parser.y +398 -0
- data/lib/csspool/css/rule_set.rb +17 -0
- data/lib/csspool/css/tokenizer.rb +231 -0
- data/lib/csspool/css/tokenizer.rex +97 -0
- data/lib/csspool/css.rb +8 -0
- data/lib/csspool/node.rb +41 -0
- data/lib/csspool/sac/document.rb +35 -0
- data/lib/csspool/sac/parser.rb +16 -0
- data/lib/csspool/sac.rb +2 -0
- data/lib/csspool/selector.rb +36 -0
- data/lib/csspool/selectors/additional.rb +6 -0
- data/lib/csspool/selectors/attribute.rb +21 -0
- data/lib/csspool/selectors/class.rb +11 -0
- data/lib/csspool/selectors/id.rb +11 -0
- data/lib/csspool/selectors/pseudo_class.rb +13 -0
- data/lib/csspool/selectors/simple.rb +22 -0
- data/lib/csspool/selectors/type.rb +6 -0
- data/lib/csspool/selectors/universal.rb +6 -0
- data/lib/csspool/selectors.rb +9 -0
- data/lib/csspool/terms/function.rb +17 -0
- data/lib/csspool/terms/hash.rb +6 -0
- data/lib/csspool/terms/ident.rb +15 -0
- data/lib/csspool/terms/number.rb +14 -0
- data/lib/csspool/terms/rgb.rb +20 -0
- data/lib/csspool/terms/string.rb +6 -0
- data/lib/csspool/terms/uri.rb +6 -0
- data/lib/csspool/terms.rb +7 -0
- data/lib/csspool/visitors/children.rb +50 -0
- data/lib/csspool/visitors/comparable.rb +84 -0
- data/lib/csspool/visitors/iterator.rb +80 -0
- data/lib/csspool/visitors/to_css.rb +248 -0
- data/lib/csspool/visitors/visitor.rb +17 -0
- data/lib/csspool/visitors.rb +5 -0
- data/lib/csspool.rb +21 -0
- data/test/css/test_document.rb +13 -0
- data/test/css/test_import_rule.rb +42 -0
- data/test/css/test_parser.rb +462 -0
- data/test/css/test_tokenizer.rb +320 -0
- data/test/helper.rb +65 -0
- data/test/sac/test_parser.rb +123 -0
- data/test/sac/test_properties.rb +43 -0
- data/test/sac/test_terms.rb +228 -0
- data/test/test_collection.rb +81 -0
- data/test/test_parser.rb +91 -0
- data/test/test_selector.rb +51 -0
- data/test/visitors/test_children.rb +20 -0
- data/test/visitors/test_comparable.rb +102 -0
- data/test/visitors/test_each.rb +19 -0
- data/test/visitors/test_to_css.rb +309 -0
- metadata +214 -0
@@ -0,0 +1,248 @@
|
|
1
|
+
module CSSPool
|
2
|
+
module Visitors
|
3
|
+
class ToCSS < Visitor
|
4
|
+
|
5
|
+
CSS_IDENTIFIER_ILLEGAL_CHARACTERS =
|
6
|
+
(0..255).to_a.pack('U*').gsub(/[a-zA-Z0-9_-]/, '')
|
7
|
+
CSS_STRING_ESCAPE_MAP = {
|
8
|
+
"\\" => "\\\\",
|
9
|
+
"\"" => "\\\"",
|
10
|
+
"\n" => "\\a ", # CSS2 4.1.3 p3.2
|
11
|
+
"\r" => "\\\r",
|
12
|
+
"\f" => "\\\f"
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@indent_level = 0
|
17
|
+
@indent_space = indent_space
|
18
|
+
end
|
19
|
+
|
20
|
+
visitor_for CSS::Document do |target|
|
21
|
+
# Default media list is []
|
22
|
+
current_media_type = []
|
23
|
+
|
24
|
+
tokens = []
|
25
|
+
|
26
|
+
target.charsets.each do |char_set|
|
27
|
+
tokens << char_set.accept(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
target.import_rules.each do |ir|
|
31
|
+
tokens << ir.accept(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
target.rule_sets.each { |rs|
|
35
|
+
if rs.media != current_media_type
|
36
|
+
media = " " + rs.media.map do |medium|
|
37
|
+
medium.name.map{ |i| i.accept(self) }.join(' ')
|
38
|
+
end.join(', ')
|
39
|
+
tokens << "#{indent}@media#{media} {"
|
40
|
+
@indent_level += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
tokens << rs.accept(self)
|
44
|
+
|
45
|
+
if rs.media != current_media_type
|
46
|
+
current_media_type = rs.media
|
47
|
+
@indent_level -= 1
|
48
|
+
tokens << "#{indent}}"
|
49
|
+
end
|
50
|
+
}
|
51
|
+
tokens.join(line_break)
|
52
|
+
end
|
53
|
+
|
54
|
+
visitor_for CSS::Charset do |target|
|
55
|
+
"@charset \"#{escape_css_string target.name}\";"
|
56
|
+
end
|
57
|
+
|
58
|
+
visitor_for CSS::ImportRule do |target|
|
59
|
+
media = ''
|
60
|
+
media = " " + target.media.map do |medium|
|
61
|
+
medium.name.map{ |i| i.accept(self) }.join(' ')
|
62
|
+
end.join(', ') if target.media.length > 0
|
63
|
+
|
64
|
+
"#{indent}@import #{target.uri.accept(self)}#{media};"
|
65
|
+
end
|
66
|
+
|
67
|
+
visitor_for CSS::RuleSet do |target|
|
68
|
+
"#{indent}" +
|
69
|
+
target.selectors.map { |sel| sel.accept self }.join(", ") + " {#{line_break}" +
|
70
|
+
target.declarations.map { |decl| decl.accept self }.join(line_break) +
|
71
|
+
"#{line_break}#{indent}}"
|
72
|
+
end
|
73
|
+
|
74
|
+
visitor_for CSS::Declaration do |target|
|
75
|
+
important = target.important? ? ' !important' : ''
|
76
|
+
|
77
|
+
indent {
|
78
|
+
"#{indent}#{escape_css_identifier target.property}: " + target.expressions.map { |exp|
|
79
|
+
|
80
|
+
op = '/' == exp.operator ? ' /' : exp.operator
|
81
|
+
|
82
|
+
[
|
83
|
+
op,
|
84
|
+
exp.accept(self),
|
85
|
+
].join ' '
|
86
|
+
}.join.strip + "#{important};"
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
90
|
+
visitor_for Terms::Ident do |target|
|
91
|
+
escape_css_identifier target.value
|
92
|
+
end
|
93
|
+
|
94
|
+
visitor_for Terms::Hash do |target|
|
95
|
+
"##{target.value}"
|
96
|
+
end
|
97
|
+
|
98
|
+
visitor_for Selectors::MediaExpression do |target|
|
99
|
+
'(' + escape_css_string(target.name) + ': ' +
|
100
|
+
target.value.map { |x| x.accept self }.join + ')'
|
101
|
+
end
|
102
|
+
|
103
|
+
visitor_for Terms::URI do |target|
|
104
|
+
"url(\"#{escape_css_string target.value}\")"
|
105
|
+
end
|
106
|
+
|
107
|
+
visitor_for Terms::Function do |target|
|
108
|
+
"#{escape_css_identifier target.name}(" +
|
109
|
+
target.params.map { |x|
|
110
|
+
[
|
111
|
+
x.operator,
|
112
|
+
x.accept(self)
|
113
|
+
].compact.join(' ')
|
114
|
+
}.join + ')'
|
115
|
+
end
|
116
|
+
|
117
|
+
visitor_for Terms::Rgb do |target|
|
118
|
+
params = [
|
119
|
+
target.red,
|
120
|
+
target.green,
|
121
|
+
target.blue
|
122
|
+
].map { |c|
|
123
|
+
c.accept(self)
|
124
|
+
}.join ', '
|
125
|
+
|
126
|
+
%{rgb(#{params})}
|
127
|
+
end
|
128
|
+
|
129
|
+
visitor_for Terms::String do |target|
|
130
|
+
"\"#{escape_css_string target.value}\""
|
131
|
+
end
|
132
|
+
|
133
|
+
visitor_for Terms::Number do |target|
|
134
|
+
[
|
135
|
+
target.unary_operator == :minus ? '-' : nil,
|
136
|
+
target.value,
|
137
|
+
target.type
|
138
|
+
].compact.join
|
139
|
+
end
|
140
|
+
|
141
|
+
visitor_for Selector do |target|
|
142
|
+
target.simple_selectors.map { |ss| ss.accept self }.join
|
143
|
+
end
|
144
|
+
|
145
|
+
visitor_for Selectors::Simple, Selectors::Universal, Selectors::Type do |target|
|
146
|
+
combo = {
|
147
|
+
:s => ' ',
|
148
|
+
:+ => ' + ',
|
149
|
+
:> => ' > '
|
150
|
+
}[target.combinator]
|
151
|
+
|
152
|
+
name = [nil, '*'].include?(target.name) ? target.name : escape_css_identifier(target.name)
|
153
|
+
[combo, name].compact.join +
|
154
|
+
target.additional_selectors.map { |as| as.accept self }.join
|
155
|
+
end
|
156
|
+
|
157
|
+
visitor_for Selectors::Id do |target|
|
158
|
+
"##{escape_css_identifier target.name}"
|
159
|
+
end
|
160
|
+
|
161
|
+
visitor_for Selectors::Class do |target|
|
162
|
+
".#{escape_css_identifier target.name}"
|
163
|
+
end
|
164
|
+
|
165
|
+
visitor_for Selectors::PseudoClass do |target|
|
166
|
+
if target.extra.nil?
|
167
|
+
":#{escape_css_identifier target.name}"
|
168
|
+
else
|
169
|
+
":#{escape_css_identifier target.name}(#{escape_css_identifier target.extra})"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
visitor_for Selectors::PseudoElement do |target|
|
174
|
+
if target.css2.nil?
|
175
|
+
"::#{escape_css_identifier target.name}"
|
176
|
+
else
|
177
|
+
":#{escape_css_identifier target.name}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
visitor_for Selectors::Attribute do |target|
|
182
|
+
case target.match_way
|
183
|
+
when Selectors::Attribute::SET
|
184
|
+
"[#{escape_css_identifier target.name}]"
|
185
|
+
when Selectors::Attribute::EQUALS
|
186
|
+
"[#{escape_css_identifier target.name}=\"#{escape_css_string target.value}\"]"
|
187
|
+
when Selectors::Attribute::INCLUDES
|
188
|
+
"[#{escape_css_identifier target.name} ~= \"#{escape_css_string target.value}\"]"
|
189
|
+
when Selectors::Attribute::DASHMATCH
|
190
|
+
"[#{escape_css_identifier target.name} |= \"#{escape_css_string target.value}\"]"
|
191
|
+
else
|
192
|
+
raise "no matching matchway"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
def indent
|
198
|
+
if block_given?
|
199
|
+
@indent_level += 1
|
200
|
+
result = yield
|
201
|
+
@indent_level -= 1
|
202
|
+
return result
|
203
|
+
end
|
204
|
+
"#{@indent_space * @indent_level}"
|
205
|
+
end
|
206
|
+
|
207
|
+
def line_break
|
208
|
+
"\n"
|
209
|
+
end
|
210
|
+
|
211
|
+
def indent_space
|
212
|
+
' '
|
213
|
+
end
|
214
|
+
|
215
|
+
def escape_css_identifier text
|
216
|
+
# CSS2 4.1.3 p2
|
217
|
+
unsafe_chars = /[#{Regexp.escape CSS_IDENTIFIER_ILLEGAL_CHARACTERS}]/
|
218
|
+
text.gsub(/^\d|^\-(?=\-|\d)|#{unsafe_chars}/um) do |char|
|
219
|
+
if ':()-\\ ='.include? char
|
220
|
+
"\\#{char}"
|
221
|
+
else # I don't trust others to handle space termination well.
|
222
|
+
"\\#{char.unpack('U').first.to_s(16).rjust(6, '0')}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def escape_css_string text
|
228
|
+
text.gsub(/[\\"\n\r\f]/) {CSS_STRING_ESCAPE_MAP[$&]}
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
class ToMinifiedCSS < ToCSS
|
233
|
+
def line_break
|
234
|
+
""
|
235
|
+
end
|
236
|
+
|
237
|
+
def indent_space
|
238
|
+
' '
|
239
|
+
end
|
240
|
+
|
241
|
+
visitor_for CSS::RuleSet do |target|
|
242
|
+
target.selectors.map { |sel| sel.accept self }.join(", ") + " {" +
|
243
|
+
target.declarations.map { |decl| decl.accept self }.join +
|
244
|
+
" }"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module CSSPool
|
2
|
+
module Visitors
|
3
|
+
class Visitor
|
4
|
+
def self.visitor_for *klasses, &block
|
5
|
+
klasses.each do |klass|
|
6
|
+
method_name = klass.name.split('::').join('_')
|
7
|
+
define_method(:"visit_#{method_name}", block)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def accept target
|
12
|
+
method_name = target.class.name.split('::').join('_')
|
13
|
+
send(:"visit_#{method_name}", target)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/csspool.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'csspool/node'
|
2
|
+
require 'csspool/selectors'
|
3
|
+
require 'csspool/terms'
|
4
|
+
require 'csspool/selector'
|
5
|
+
old = $-w
|
6
|
+
$-w = false
|
7
|
+
require 'csspool/css/parser'
|
8
|
+
$-w = old
|
9
|
+
require 'csspool/css/tokenizer'
|
10
|
+
require 'csspool/sac'
|
11
|
+
require 'csspool/css'
|
12
|
+
require 'csspool/visitors'
|
13
|
+
require 'csspool/collection'
|
14
|
+
|
15
|
+
module CSSPool
|
16
|
+
VERSION = "3.1.0"
|
17
|
+
|
18
|
+
def self.CSS doc
|
19
|
+
CSSPool::CSS::Document.parse doc
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module CSSPool
|
4
|
+
module CSS
|
5
|
+
class TestDocument < CSSPool::TestCase
|
6
|
+
def test_search
|
7
|
+
doc = CSSPool.CSS('div > p { background: red; }')
|
8
|
+
assert_equal 1, doc['div > p'].length
|
9
|
+
assert_equal 1, doc['div > p'].first.declarations.length
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
module CSSPool
|
4
|
+
module CSS
|
5
|
+
class TestImportRule < CSSPool::TestCase
|
6
|
+
def test_import
|
7
|
+
doc = CSSPool.CSS <<-eocss
|
8
|
+
@import "foo.css";
|
9
|
+
eocss
|
10
|
+
|
11
|
+
assert_equal 1, doc.import_rules.length
|
12
|
+
|
13
|
+
doc.import_rules.each do |ir|
|
14
|
+
new_doc = ir.load do |url|
|
15
|
+
assert_equal "foo.css", url
|
16
|
+
"div { background: red; }"
|
17
|
+
end
|
18
|
+
assert new_doc
|
19
|
+
assert_equal 1, new_doc.rule_sets.length
|
20
|
+
assert_equal ir, new_doc.parent_import_rule
|
21
|
+
assert_equal doc, new_doc.parent
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_import_with_media
|
26
|
+
doc = CSSPool.CSS <<-eocss
|
27
|
+
@import "foo.css" screen, print;
|
28
|
+
eocss
|
29
|
+
|
30
|
+
assert_equal 1, doc.import_rules.length
|
31
|
+
doc.import_rules.each do |ir|
|
32
|
+
new_doc = ir.load do |url|
|
33
|
+
"div { background: red; }"
|
34
|
+
end
|
35
|
+
new_doc.rule_sets.each do |rs|
|
36
|
+
assert_equal ir.media, rs.media
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|