1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
module CodeRay
module Scanners
class CSS < Scanner
register_for :css
KINDS_NOT_LOC = [
:comment,
:class, :pseudo_class, :type,
:constant, :directive,
:key, :value, :operator, :color, :float,
:error, :important,
]
module RE
Hex = /[0-9a-fA-F]/
Unicode = /\\#{Hex}{1,6}(?:\r\n|\s)?/ # differs from standard because it allows uppercase hex too
Escape = /#{Unicode}|\\[^\r\n\f0-9a-fA-F]/
NMChar = /[-_a-zA-Z0-9]|#{Escape}/
NMStart = /[_a-zA-Z]|#{Escape}/
NL = /\r\n|\r|\n|\f/
String1 = /"(?:[^\n\r\f\\"]|\\#{NL}|#{Escape})*"?/ # FIXME: buggy regexp
String2 = /'(?:[^\n\r\f\\']|\\#{NL}|#{Escape})*'?/ # FIXME: buggy regexp
String = /#{String1}|#{String2}/
HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/
Color = /#{HexColor}/
Num = /-?(?:[0-9]+|[0-9]*\.[0-9]+)/
Name = /#{NMChar}+/
Ident = /-?#{NMStart}#{NMChar}*/
AtKeyword = /@#{Ident}/
Percentage = /#{Num}%/
reldimensions = %w[em ex px]
absdimensions = %w[in cm mm pt pc]
Unit = Regexp.union(*(reldimensions + absdimensions))
Dimension = /#{Num}#{Unit}/
Comment = %r! /\* (?: .*? \*/ | .* ) !mx
Function = /(?:url|alpha)\((?:[^)\n\r\f]|\\\))*\)?/
Id = /##{Name}/
Class = /\.#{Name}/
PseudoClass = /:#{Name}/
AttributeSelector = /\[[^\]]*\]?/
end
def scan_tokens tokens, options
value_expected = nil
states = [:initial]
until eos?
kind = nil
match = nil
if scan(/\s+/)
kind = :space
elsif case states.last
when :initial, :media
if scan(/(?>#{RE::Ident})(?!\()|\*/ox)
kind = :type
elsif scan RE::Class
kind = :class
elsif scan RE::Id
kind = :constant
elsif scan RE::PseudoClass
kind = :pseudo_class
elsif match = scan(RE::AttributeSelector)
# TODO: Improve highlighting inside of attribute selectors.
tokens << [:open, :string]
tokens << [match[0,1], :delimiter]
tokens << [match[1..-2], :content] if match.size > 2
tokens << [match[-1,1], :delimiter] if match[-1] == ?]
tokens << [:close, :string]
next
elsif match = scan(/@media/)
kind = :directive
states.push :media_before_name
end
when :block
if scan(/(?>#{RE::Ident})(?!\()/ox)
if value_expected
kind = :value
else
kind = :key
end
end
when :media_before_name
if scan RE::Ident
kind = :type
states[-1] = :media_after_name
end
when :media_after_name
if scan(/\{/)
kind = :operator
states[-1] = :media
end
when :comment
if scan(/(?:[^*\s]|\*(?!\/))+/)
kind = :comment
elsif scan(/\*\//)
kind = :comment
states.pop
elsif scan(/\s+/)
kind = :space
end
else
raise_inspect 'Unknown state', tokens
end
elsif scan(/\/\*/)
kind = :comment
states.push :comment
elsif scan(/\{/)
value_expected = false
kind = :operator
states.push :block
elsif scan(/\}/)
value_expected = false
if states.last == :block || states.last == :media
kind = :operator
states.pop
else
kind = :error
end
elsif match = scan(/#{RE::String}/o)
tokens << [:open, :string]
tokens << [match[0, 1], :delimiter]
tokens << [match[1..-2], :content] if match.size > 2
tokens << [match[-1, 1], :delimiter] if match.size >= 2
tokens << [:close, :string]
next
elsif match = scan(/#{RE::Function}/o)
tokens << [:open, :string]
start = match[/^\w+\(/]
tokens << [start, :delimiter]
if match[-1] == ?)
tokens << [match[start.size..-2], :content]
tokens << [')', :delimiter]
else
tokens << [match[start.size..-1], :content]
end
tokens << [:close, :string]
next
elsif scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
kind = :float
elsif scan(/#{RE::Color}/o)
kind = :color
elsif scan(/! *important/)
kind = :important
elsif scan(/rgb\([^()\n]*\)?/)
kind = :color
elsif scan(/#{RE::AtKeyword}/o)
kind = :directive
elsif match = scan(/ [+>:;,.=()\/] /x)
if match == ':'
value_expected = true
elsif match == ';'
value_expected = false
end
kind = :operator
else
getch
kind = :error
end
match ||= matched
if $CODERAY_DEBUG and not kind
raise_inspect 'Error token %p in line %d' %
[[match, kind], line], tokens
end
raise_inspect 'Empty token', tokens unless match
tokens << [match, kind]
end
tokens
end
end
end
end
|