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