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