csspool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,29 @@
1
+ module CSS
2
+ module SAC
3
+ class Lexeme
4
+ attr_reader :name, :pattern
5
+
6
+ def initialize(name, pattern=nil, &block)
7
+ raise ArgumentError, "name required" unless name
8
+
9
+ @name = name
10
+ patterns = []
11
+
12
+ patterns << pattern if pattern
13
+ yield(patterns) if block_given?
14
+
15
+ if patterns.empty?
16
+ raise ArgumentError, "at least one pattern required"
17
+ end
18
+
19
+ patterns.collect! do |pattern|
20
+ source = pattern.source
21
+ source = "\\A#{source}"
22
+ Regexp.new(source, Regexp::IGNORECASE + Regexp::MULTILINE)
23
+ end
24
+
25
+ @pattern = Regexp.union(*patterns)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,111 @@
1
+ module CSS
2
+ module SAC
3
+ class LexicalUnit
4
+ attr_accessor :dimension_unit_text,
5
+ :lexical_unit_type,
6
+ :float_value,
7
+ :integer_value,
8
+ :string_value,
9
+ :parameters,
10
+ :function_name
11
+
12
+ alias :to_s :string_value
13
+ end
14
+
15
+ class Function < LexicalUnit
16
+ FUNCTIONS = {
17
+ 'counter' => :SAC_COUNTER_FUNCTION,
18
+ 'counters' => :SAC_COUNTERS_FUNCTION,
19
+ 'rect' => :SAC_RECT_FUNCTION,
20
+ }
21
+ def initialize(name, params)
22
+ self.string_value = "#{name}#{params.join(', ')})"
23
+ name =~ /^(.*)\(/
24
+ self.function_name = $1
25
+ self.parameters = params
26
+ self.lexical_unit_type = FUNCTIONS[self.function_name] || :SAC_FUNCTION
27
+ end
28
+ end
29
+
30
+ class Color < LexicalUnit
31
+ def initialize(value)
32
+ self.string_value = value
33
+ self.lexical_unit_type = :SAC_RGBCOLOR
34
+ if value =~ /^#(\d{1,2})(\d{1,2})(\d{1,2})$/
35
+ self.parameters = [
36
+ Number.new($1.hex, '', :SAC_INTEGER),
37
+ Number.new($2.hex, '', :SAC_INTEGER),
38
+ Number.new($3.hex, '', :SAC_INTEGER)
39
+ ]
40
+ else
41
+ self.parameters = [LexicalIdent.new(value)]
42
+ end
43
+ end
44
+ end
45
+
46
+ class LexicalString < LexicalUnit
47
+ def initialize(value)
48
+ self.string_value = value
49
+ self.lexical_unit_type = :SAC_STRING_VALUE
50
+ end
51
+ end
52
+
53
+ class LexicalIdent < LexicalUnit
54
+ def initialize(value)
55
+ self.string_value = value
56
+ self.lexical_unit_type = :SAC_IDENT
57
+ end
58
+ end
59
+
60
+ class LexicalURI < LexicalUnit
61
+ def initialize(value)
62
+ self.string_value = value.gsub(/^url\(/, '').gsub(/\)$/, '')
63
+ self.lexical_unit_type = :SAC_URI
64
+ end
65
+ end
66
+
67
+ class Number < LexicalUnit
68
+ NON_NEGATIVE_UNITS = [
69
+ :SAC_DEGREE,
70
+ :SAC_GRADIAN,
71
+ :SAC_RADIAN,
72
+ :SAC_MILLISECOND,
73
+ :SAC_SECOND,
74
+ :SAC_HERTZ,
75
+ :SAC_KILOHERTZ,
76
+ ]
77
+ UNITS = {
78
+ 'deg' => :SAC_DEGREE,
79
+ 'rad' => :SAC_RADIAN,
80
+ 'grad' => :SAC_GRADIAN,
81
+ 'ms' => :SAC_MILLISECOND,
82
+ 's' => :SAC_SECOND,
83
+ 'hz' => :SAC_HERTZ,
84
+ 'khz' => :SAC_KILOHERTZ,
85
+ 'px' => :SAC_PIXEL,
86
+ 'cm' => :SAC_CENTIMETER,
87
+ 'mm' => :SAC_MILLIMETER,
88
+ 'in' => :SAC_INCH,
89
+ 'pt' => :SAC_POINT,
90
+ 'pc' => :SAC_PICA,
91
+ '%' => :SAC_PERCENTAGE,
92
+ 'em' => :SAC_EM,
93
+ 'ex' => :SAC_EX,
94
+ }
95
+ def initialize(value, unit = nil, type = nil)
96
+ if value.is_a?(String)
97
+ value =~ /^(-?[0-9.]*)(.*)$/
98
+ value = $1
99
+ unit ||= $2
100
+ end
101
+ type ||= UNITS[self.dimension_unit_text]
102
+ self.string_value = "#{value}#{unit}"
103
+ self.float_value = value.to_f
104
+ self.integer_value = value.to_i
105
+ self.dimension_unit_text = unit.downcase
106
+ self.lexical_unit_type = UNITS[self.dimension_unit_text] ||
107
+ (value =~ /\./ ? :SAC_NUMBER : :SAC_INTEGER)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,6 @@
1
+ module CSS
2
+ module SAC
3
+ class ParseException < RuntimeError
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,98 @@
1
+ require "css/sac/document_handler"
2
+ require "css/sac/error_handler"
3
+ require "css/sac/generated_parser"
4
+ require "css/sac/lexical_unit"
5
+ require "css/sac/parse_exception"
6
+ require "css/sac/tokenizer"
7
+ require "css/sac/property_parser"
8
+ require "css/sac/condition"
9
+ require "css/sac/attribute_condition"
10
+ require "css/sac/selectors"
11
+
12
+ module CSS
13
+ module SAC
14
+ class Parser < CSS::SAC::GeneratedParser
15
+ # The version of CSSPool you're using
16
+ VERSION = '0.1.0'
17
+
18
+ TOKENIZER = Tokenizer.new
19
+
20
+ attr_accessor :document_handler, :error_handler, :logger
21
+
22
+ def initialize(document_handler = nil)
23
+ @error_handler = ErrorHandler.new
24
+ @document_handler = document_handler || DocumentHandler.new()
25
+ @property_parser = PropertyParser.new()
26
+ @tokenizer = TOKENIZER
27
+ @logger = nil
28
+ end
29
+
30
+ def parse_style_sheet(string)
31
+ @yydebug = true
32
+ @tokens = TOKENIZER.tokenize(string)
33
+ @position = 0
34
+
35
+ self.document_handler.start_document(string)
36
+ do_parse
37
+ self.document_handler.end_document(string)
38
+ end
39
+
40
+ alias :parse :parse_style_sheet
41
+ alias :parse_rule :parse_style_sheet
42
+
43
+ # Returns the parser version. We return CSS2, but its actually
44
+ # CSS2.1. No font-face tags. Sorry.
45
+ def parser_version
46
+ "http://www.w3.org/TR/REC-CSS2"
47
+ end
48
+
49
+ attr_reader :property_parser
50
+ attr_reader :tokenizer
51
+
52
+ private # Bro.
53
+
54
+ # We have to eliminate matching pairs.
55
+ # http://www.w3.org/TR/CSS21/syndata.html#parsing-errors
56
+ # See the malformed declarations section
57
+ def eliminate_pair_matches(error_value)
58
+ pairs = {}
59
+ pairs['"'] = '"'
60
+ pairs["'"] = "'"
61
+ pairs['{'] = '}'
62
+ pairs['['] = ']'
63
+ pairs['('] = ')'
64
+
65
+ error_value.strip!
66
+ if pairs[error_value]
67
+ logger.warn("Eliminating pair for: #{error_value}") if logger
68
+ loop {
69
+ token = next_token
70
+ eliminate_pair_matches(token[1])
71
+ logger.warn("Eliminated token: #{token.join(' ')}") if logger
72
+ break if token[1] == pairs[error_value]
73
+ }
74
+ end
75
+ end
76
+
77
+ def on_error(error_token_id, error_value, value_stack)
78
+ if logger
79
+ logger.error(token_to_str(error_token_id))
80
+ logger.error("error value: #{error_value}")
81
+ end
82
+ eliminate_pair_matches(error_value)
83
+ end
84
+
85
+ def next_token
86
+ return [false, false] if @position >= @tokens.length
87
+
88
+ n_token = @tokens[@position]
89
+ @position += 1
90
+ if n_token.name == :COMMENT
91
+ self.document_handler.comment(n_token.value)
92
+ return next_token
93
+ end
94
+ n_token.to_racc_token
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,47 @@
1
+ require "css/sac/generated_property_parser"
2
+
3
+ module CSS
4
+ module SAC
5
+ class PropertyParser < CSS::SAC::GeneratedPropertyParser
6
+ def initialize
7
+ @tokens = []
8
+ @token_table = Racc_arg[10]
9
+ end
10
+
11
+ def parse_tokens(tokens)
12
+ negate = false # Nasty hack for unary minus
13
+ @tokens = tokens.find_all { |x| x.name != :S }.map { |token|
14
+ tok = if @token_table.has_key?(token.value)
15
+ [token.value, token.value]
16
+ else
17
+ if token.name == :delim && !@token_table.has_key?(token.value)
18
+ negate = true if token.value == '-'
19
+ nil
20
+ else
21
+ token.to_racc_token
22
+ end
23
+ end
24
+
25
+ if negate && tok
26
+ tok[1] = "-#{tok[1]}"
27
+ negate = false
28
+ end
29
+
30
+ tok
31
+ }.compact
32
+
33
+ begin
34
+ return do_parse
35
+ rescue ParseError => e
36
+ return nil
37
+ end
38
+ end
39
+
40
+ private
41
+ def next_token
42
+ return [false, false] if @tokens.empty?
43
+ @tokens.shift
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,87 @@
1
+ module CSS
2
+ module SAC
3
+ class Selector
4
+ attr_reader :selector_type
5
+ end
6
+
7
+ class SimpleSelector < Selector
8
+ def initialize
9
+ @selector_type = :SAC_ANY_NODE_SELECTOR
10
+ end
11
+
12
+ def to_css
13
+ case @selector_type
14
+ when :SAC_ANY_NODE_SELECTOR
15
+ '*'
16
+ end
17
+ end
18
+ end
19
+
20
+ class ElementSelector < SimpleSelector
21
+ attr_reader :local_name
22
+ alias :name :local_name
23
+
24
+ def initialize(name)
25
+ super()
26
+ @selector_type = :SAC_ELEMENT_NODE_SELECTOR
27
+ @local_name = name
28
+ end
29
+
30
+ def to_css
31
+ local_name
32
+ end
33
+ end
34
+
35
+ class ConditionalSelector < SimpleSelector
36
+ attr_accessor :condition, :simple_selector
37
+ alias :selector :simple_selector
38
+
39
+ def initialize(selector, condition)
40
+ @condition = condition
41
+ @simple_selector = selector
42
+ @selector_type = :SAC_CONDITIONAL_SELECTOR
43
+ end
44
+
45
+ def to_css
46
+ [selector, condition].map { |x|
47
+ x ? x.to_css : ''
48
+ }.join('')
49
+ end
50
+ end
51
+
52
+ class DescendantSelector < SimpleSelector
53
+ attr_accessor :ancestor_selector, :simple_selector
54
+ alias :selector :simple_selector
55
+
56
+ def initialize(ancestor, selector, type)
57
+ @ancestor_selector = ancestor
58
+ @simple_selector = selector
59
+ @selector_type = type
60
+ end
61
+
62
+ def to_css
63
+ descent_token =
64
+ case @selector_type
65
+ when :SAC_CHILD_SELECTOR
66
+ ' > '
67
+ when :SAC_DESCENDANT_SELECTOR
68
+ ' '
69
+ end
70
+ ancestor_selector.to_css + descent_token + selector.to_css
71
+ end
72
+ end
73
+
74
+ class SiblingSelector < SimpleSelector
75
+ attr_accessor :selector, :sibling_selector
76
+ def initialize(selector, sibling)
77
+ @selector = selector
78
+ @sibling_selector = sibling
79
+ @selector_type = :SAC_DIRECT_ADJACENT_SELECTOR
80
+ end
81
+
82
+ def to_css
83
+ selector.to_css + ' + ' + sibling_selector.to_css
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,27 @@
1
+ module CSS
2
+ module SAC
3
+ class Token
4
+ attr_reader :name, :value, :position
5
+
6
+ def initialize(name, value, position)
7
+ @name = name
8
+ @value = value
9
+ @position = position
10
+ end
11
+
12
+ def to_racc_token
13
+ [name, value]
14
+ end
15
+ end
16
+
17
+ class DelimiterToken < Token
18
+ def initialize(value, position)
19
+ super(:delim, value, position)
20
+ end
21
+
22
+ def to_racc_token
23
+ [value, value]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,186 @@
1
+ require "css/sac/lexeme"
2
+ require "css/sac/token"
3
+
4
+ module CSS
5
+ module SAC
6
+ class Tokenizer
7
+ def initialize(&block)
8
+ @lexemes = []
9
+ @macros = {}
10
+
11
+ # http://www.w3.org/TR/CSS21/syndata.html
12
+ macro(:h, /([0-9a-f])/ )
13
+ macro(:nonascii, /([\200-\377])/ )
14
+ macro(:nl, /(\n|\r\n|\r|\f)/ )
15
+ macro(:unicode, /(\\#{m(:h)}{1,6}(\r\n|[ \t\r\n\f])?)/ )
16
+ macro(:escape, /(#{m(:unicode)}|\\[^\r\n\f0-9a-f])/ )
17
+ macro(:nmstart, /([_a-z]|#{m(:nonascii)}|#{m(:escape)})/ )
18
+ macro(:nmchar, /([_a-z0-9-]|#{m(:nonascii)}|#{m(:escape)})/ )
19
+ macro(:string1, /(\"([^\n\r\f\\\"]|\\#{m(:nl)}|#{m(:escape)})*\")/ )
20
+ macro(:string2, /(\'([^\n\r\f\\']|\\#{m(:nl)}|#{m(:escape)})*\')/ )
21
+ macro(:invalid1, /(\"([^\n\r\f\\\"]|\\#{m(:nl)}|#{m(:escape)})*)/ )
22
+ macro(:invalid2, /(\'([^\n\r\f\\']|\\#{m(:nl)}|#{m(:escape)})*)/ )
23
+ macro(:comment, /(\/\*[^*]*\*+([^\/*][^*]*\*+)*\/)/ )
24
+ macro(:ident, /(-?#{m(:nmstart)}#{m(:nmchar)}*)/ )
25
+ macro(:name, /(#{m(:nmchar)}+)/ )
26
+ macro(:num, /([0-9]+|[0-9]*\.[0-9]+)/ )
27
+ macro(:string, /(#{m(:string1)}|#{m(:string2)})/ )
28
+ macro(:invalid, /(#{m(:invalid1)}|#{m(:invalid2)})/ )
29
+ macro(:url, /(([!#\$%&*-~]|#{m(:nonascii)}|#{m(:escape)})*)/ )
30
+ macro(:s, /([ \t\r\n\f]+)/ )
31
+ macro(:w, /(#{m(:s)}?)/ )
32
+ macro(:A, /(a|\\0{0,4}(41|61)(\r\n|[ \t\r\n\f])?)/ )
33
+ macro(:C, /(c|\\0{0,4}(43|63)(\r\n|[ \t\r\n\f])?)/ )
34
+ macro(:D, /(d|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?)/ )
35
+ macro(:E, /(e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?)/ )
36
+ macro(:G, /(g|\\0{0,4}(47|67)(\r\n|[ \t\r\n\f])?|\\g)/ )
37
+ macro(:H, /(h|\\0{0,4}(48|68)(\r\n|[ \t\r\n\f])?|\\h)/ )
38
+ macro(:I, /(i|\\0{0,4}(49|69)(\r\n|[ \t\r\n\f])?|\\i)/ )
39
+ macro(:K, /(k|\\0{0,4}(4b|6b)(\r\n|[ \t\r\n\f])?|\\k)/ )
40
+ macro(:M, /(m|\\0{0,4}(4d|6d)(\r\n|[ \t\r\n\f])?|\\m)/ )
41
+ macro(:N, /(n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n)/ )
42
+ macro(:O, /(o|\\0{0,4}(51|71)(\r\n|[ \t\r\n\f])?|\\o)/ )
43
+ macro(:P, /(p|\\0{0,4}(50|70)(\r\n|[ \t\r\n\f])?|\\p)/ )
44
+ macro(:R, /(r|\\0{0,4}(52|72)(\r\n|[ \t\r\n\f])?|\\r)/ )
45
+ macro(:S, /(s|\\0{0,4}(53|73)(\r\n|[ \t\r\n\f])?|\\s)/ )
46
+ macro(:T, /(t|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t)/ )
47
+ macro(:X, /(x|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\x)/ )
48
+ macro(:Z, /(z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z)/ )
49
+
50
+ #token :COMMENT do |patterns|
51
+ # patterns << /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//
52
+ # patterns << /#{m(:s)}+\/\*[^*]*\*+([^\/*][^*]*\*+)*\//
53
+ #end
54
+
55
+ token(:LBRACE, /#{m(:w)}\{/)
56
+ token(:PLUS, /#{m(:w)}\+/)
57
+ token(:GREATER, /#{m(:w)}>/)
58
+ token(:COMMA, /#{m(:w)},/)
59
+
60
+ token(:S, /#{m(:s)}/)
61
+
62
+ #token :URI do |patterns|
63
+ # patterns << /url\(#{m(:w)}#{m(:string)}#{m(:w)}\)/
64
+ # patterns << /url\(#{m(:w)}#{m(:url)}#{m(:w)}\)/
65
+ #end
66
+
67
+ token(:FUNCTION, /#{m(:ident)}\(/)
68
+ token(:IDENT, /#{m(:ident)}/)
69
+
70
+ token(:CDO, /<!--/)
71
+ token(:CDC, /-->/)
72
+ token(:INCLUDES, /~=/)
73
+ token(:DASHMATCH, /\|=/)
74
+ #token(:STRING, /#{m(:string)}/)
75
+ token(:INVALID, /#{m(:invalid)}/)
76
+ token(:HASH, /##{m(:name)}/)
77
+ token(:IMPORT_SYM, /@#{m(:I)}#{m(:M)}#{m(:P)}#{m(:O)}#{m(:R)}#{m(:T)}/)
78
+ token(:PAGE_SYM, /@#{m(:P)}#{m(:A)}#{m(:G)}#{m(:E)}/)
79
+ token(:MEDIA_SYM, /@#{m(:M)}#{m(:E)}#{m(:D)}#{m(:I)}#{m(:A)}/)
80
+ token(:CHARSET_SYM, /@#{m(:C)}#{m(:H)}#{m(:A)}#{m(:R)}#{m(:S)}#{m(:E)}#{m(:T)}/)
81
+ token(:IMPORTANT_SYM, /!(#{m(:w)}|#{m(:comment)})*#{m(:I)}#{m(:M)}#{m(:P)}#{m(:O)}#{m(:R)}#{m(:T)}#{m(:A)}#{m(:N)}#{m(:T)}/)
82
+ token(:EMS, /#{m(:num)}#{m(:E)}#{m(:M)}/)
83
+ token(:EXS, /#{m(:num)}#{m(:E)}#{m(:X)}/)
84
+
85
+ token :LENGTH do |patterns|
86
+ patterns << /#{m(:num)}#{m(:P)}#{m(:X)}/
87
+ patterns << /#{m(:num)}#{m(:C)}#{m(:M)}/
88
+ patterns << /#{m(:num)}#{m(:M)}#{m(:M)}/
89
+ patterns << /#{m(:num)}#{m(:I)}#{m(:N)}/
90
+ patterns << /#{m(:num)}#{m(:P)}#{m(:T)}/
91
+ patterns << /#{m(:num)}#{m(:P)}#{m(:C)}/
92
+ end
93
+
94
+ token :ANGLE do |patterns|
95
+ patterns << /#{m(:num)}#{m(:D)}#{m(:E)}#{m(:G)}/
96
+ patterns << /#{m(:num)}#{m(:R)}#{m(:A)}#{m(:D)}/
97
+ patterns << /#{m(:num)}#{m(:G)}#{m(:R)}#{m(:A)}#{m(:D)}/
98
+ end
99
+
100
+ token :TIME do |patterns|
101
+ patterns << /#{m(:num)}#{m(:M)}#{m(:S)}/
102
+ patterns << /#{m(:num)}#{m(:S)}/
103
+ end
104
+
105
+ token :FREQ do |patterns|
106
+ patterns << /#{m(:num)}#{m(:H)}#{m(:Z)}/
107
+ patterns << /#{m(:num)}#{m(:K)}#{m(:H)}#{m(:Z)}/
108
+ end
109
+
110
+ token(:DIMENSION, /#{m(:num)}#{m(:ident)}/)
111
+ token(:PERCENTAGE, /#{m(:num)}%/)
112
+ token(:NUMBER, /#{m(:num)}/)
113
+
114
+
115
+ yield self if block_given?
116
+ end
117
+
118
+ def tokenize(input_data)
119
+ tokens = []
120
+ pos = 0
121
+
122
+ comments = input_data.scan(/\/\*[^*]*\*+\//m)
123
+ non_comments = input_data.split(/\/\*[^*]*\*+\//m)
124
+
125
+ # Handle a small edge case, if our CSS is *only* comments,
126
+ # the split, zip, scan trick won't work
127
+ if non_comments.length == 0
128
+ tokens = comments.map { |x| Token.new(:COMMENT, x, nil) }
129
+ else
130
+ non_comments.zip(comments).each do |non_comment, comment|
131
+ non_comment.split(/url\([^\)]*\)/m).zip(
132
+ non_comment.scan(/url\([^\)]*\)/m)
133
+ ).each do |non_url, url|
134
+ non_url.split(/"[^"]*"|'[^']*'/m).zip(
135
+ non_url.scan(/"[^"]*"|'[^']*'/m)
136
+ ).each do |non_string, quoted_string|
137
+ if non_string.length > 0 && non_string =~ /\A\s*\Z/m
138
+ tokens << Token.new(:S, non_string, nil)
139
+ else
140
+ non_string.split(/[ \t\r\n\f]*(?![{}+>]*)/m).zip(
141
+ non_string.scan(/[ \t\r\n\f]*(?![{}+>]*)/m)
142
+ ).each do |string, whitespace|
143
+ until string.empty?
144
+ token = nil
145
+ @lexemes.each do |lexeme|
146
+ match = lexeme.pattern.match(string)
147
+ if match
148
+ token = Token.new(lexeme.name, match.to_s, pos)
149
+ break
150
+ end
151
+ end
152
+
153
+ token ||= DelimiterToken.new(/^./.match(string).to_s, pos)
154
+
155
+ tokens << token
156
+ string = string.slice(Range.new(token.value.length, -1))
157
+ pos += token.value.length
158
+ end
159
+ tokens << Token.new(:S, whitespace, nil) if whitespace
160
+ end
161
+ end
162
+ tokens << Token.new(:STRING, quoted_string, nil) if quoted_string
163
+ end
164
+ tokens << Token.new(:URI, url, nil) if url
165
+ end
166
+ tokens << Token.new(:COMMENT, comment, nil) if comment
167
+ end
168
+ end
169
+
170
+ tokens
171
+ end
172
+
173
+ private
174
+
175
+ def token(name, pattern=nil, &block)
176
+ @lexemes << Lexeme.new(name, pattern, &block)
177
+ end
178
+
179
+ def macro(name, regex=nil)
180
+ regex ? @macros[name] = regex : @macros[name].source
181
+ end
182
+
183
+ alias :m :macro
184
+ end
185
+ end
186
+ end
data/lib/css/sac.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "css/sac/parser"
2
+
3
+ module CSS
4
+ module SAC
5
+ class << self
6
+ def parse(text)
7
+ parser = CSS::SAC::Parser.new
8
+ parser.parse(text)
9
+ parser
10
+ end
11
+ end
12
+ end
13
+ end