csspool 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +4 -0
- data/Manifest.txt +32 -0
- data/README.txt +71 -0
- data/Rakefile +56 -0
- data/lib/css/sac/attribute_condition.rb +78 -0
- data/lib/css/sac/condition.rb +21 -0
- data/lib/css/sac/document_handler.rb +67 -0
- data/lib/css/sac/error_handler.rb +14 -0
- data/lib/css/sac/generated_property_parser.rb +9214 -0
- data/lib/css/sac/lexeme.rb +29 -0
- data/lib/css/sac/lexical_unit.rb +111 -0
- data/lib/css/sac/parse_exception.rb +6 -0
- data/lib/css/sac/parser.rb +98 -0
- data/lib/css/sac/property_parser.rb +47 -0
- data/lib/css/sac/selectors.rb +87 -0
- data/lib/css/sac/token.rb +27 -0
- data/lib/css/sac/tokenizer.rb +186 -0
- data/lib/css/sac.rb +13 -0
- data/lib/parser.y +287 -0
- data/lib/property_parser.y +2346 -0
- data/lib/property_parser.y.erb +1321 -0
- data/test/helper.rb +7 -0
- data/test/test_all.rb +4 -0
- data/test/test_lexeme.rb +39 -0
- data/test/test_lexical_unit.rb +96 -0
- data/test/test_parse_error.rb +199 -0
- data/test/test_parser.rb +183 -0
- data/test/test_property_parser.rb +593 -0
- data/test/test_selector_as_string.rb +83 -0
- data/test/test_selector_parser.rb +170 -0
- data/test/test_token.rb +24 -0
- data/test/test_tokenizer.rb +117 -0
- metadata +88 -0
@@ -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,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
|