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
|
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, :string,
:error, :important,
] # :nodoc:
module RE # :nodoc:
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|attr|counters?)\((?:[^)\n\r\f]|\\\))*\)?/
Id = /##{Name}/
Class = /\.#{Name}/
PseudoClass = /:#{Name}/
AttributeSelector = /\[[^\]]*\]?/
end
protected
def scan_tokens encoder, options
value_expected = nil
states = [:initial]
until eos?
if match = scan(/\s+/)
encoder.text_token match, :space
elsif case states.last
when :initial, :media
if match = scan(/(?>#{RE::Ident})(?!\()|\*/ox)
encoder.text_token match, :type
elsif match = scan(RE::Class)
encoder.text_token match, :class
elsif match = scan(RE::Id)
encoder.text_token match, :constant
elsif match = scan(RE::PseudoClass)
encoder.text_token match, :pseudo_class
elsif match = scan(RE::AttributeSelector)
# TODO: Improve highlighting inside of attribute selectors.
encoder.text_token match[0,1], :operator
encoder.text_token match[1..-2], :attribute_name if match.size > 2
encoder.text_token match[-1,1], :operator if match[-1] == ?]
elsif match = scan(/@media/)
encoder.text_token match, :directive
states.push :media_before_name
end
when :block
if match = scan(/(?>#{RE::Ident})(?!\()/ox)
if value_expected
encoder.text_token match, :value
else
encoder.text_token match, :key
end
end
when :media_before_name
if match = scan(RE::Ident)
encoder.text_token match, :type
states[-1] = :media_after_name
end
when :media_after_name
if match = scan(/\{/)
encoder.text_token match, :operator
states[-1] = :media
end
else
#:nocov:
raise_inspect 'Unknown state', encoder
#:nocov:
end
elsif match = scan(/\/\*(?:.*?\*\/|\z)/m)
encoder.text_token match, :comment
elsif match = scan(/\{/)
value_expected = false
encoder.text_token match, :operator
states.push :block
elsif match = scan(/\}/)
value_expected = false
if states.last == :block || states.last == :media
encoder.text_token match, :operator
states.pop
else
encoder.text_token match, :error
end
elsif match = scan(/#{RE::String}/o)
encoder.begin_group :string
encoder.text_token match[0, 1], :delimiter
encoder.text_token match[1..-2], :content if match.size > 2
encoder.text_token match[-1, 1], :delimiter if match.size >= 2
encoder.end_group :string
elsif match = scan(/#{RE::Function}/o)
encoder.begin_group :string
start = match[/^\w+\(/]
encoder.text_token start, :delimiter
if match[-1] == ?)
encoder.text_token match[start.size..-2], :content
encoder.text_token ')', :delimiter
else
encoder.text_token match[start.size..-1], :content
end
encoder.end_group :string
elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
encoder.text_token match, :float
elsif match = scan(/#{RE::Color}/o)
encoder.text_token match, :color
elsif match = scan(/! *important/)
encoder.text_token match, :important
elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/)
encoder.text_token match, :color
elsif match = scan(RE::AtKeyword)
encoder.text_token match, :directive
elsif match = scan(/ [+>:;,.=()\/] /x)
if match == ':'
value_expected = true
elsif match == ';'
value_expected = false
end
encoder.text_token match, :operator
else
encoder.text_token getch, :error
end
end
encoder
end
end
end
end
|