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