Chris@909: module CodeRay Chris@909: module Scanners Chris@909: Chris@909: class CSS < Scanner Chris@909: Chris@909: register_for :css Chris@909: Chris@909: KINDS_NOT_LOC = [ Chris@909: :comment, Chris@909: :class, :pseudo_class, :type, Chris@909: :constant, :directive, Chris@909: :key, :value, :operator, :color, :float, :string, Chris@909: :error, :important, Chris@909: ] # :nodoc: Chris@909: Chris@909: module RE # :nodoc: Chris@909: Hex = /[0-9a-fA-F]/ Chris@909: Unicode = /\\#{Hex}{1,6}(?:\r\n|\s)?/ # differs from standard because it allows uppercase hex too Chris@909: Escape = /#{Unicode}|\\[^\r\n\f0-9a-fA-F]/ Chris@909: NMChar = /[-_a-zA-Z0-9]|#{Escape}/ Chris@909: NMStart = /[_a-zA-Z]|#{Escape}/ Chris@909: NL = /\r\n|\r|\n|\f/ Chris@909: String1 = /"(?:[^\n\r\f\\"]|\\#{NL}|#{Escape})*"?/ # TODO: buggy regexp Chris@909: String2 = /'(?:[^\n\r\f\\']|\\#{NL}|#{Escape})*'?/ # TODO: buggy regexp Chris@909: String = /#{String1}|#{String2}/ Chris@909: Chris@909: HexColor = /#(?:#{Hex}{6}|#{Hex}{3})/ Chris@909: Color = /#{HexColor}/ Chris@909: Chris@909: Num = /-?(?:[0-9]+|[0-9]*\.[0-9]+)/ Chris@909: Name = /#{NMChar}+/ Chris@909: Ident = /-?#{NMStart}#{NMChar}*/ Chris@909: AtKeyword = /@#{Ident}/ Chris@909: Percentage = /#{Num}%/ Chris@909: Chris@909: reldimensions = %w[em ex px] Chris@909: absdimensions = %w[in cm mm pt pc] Chris@909: Unit = Regexp.union(*(reldimensions + absdimensions)) Chris@909: Chris@909: Dimension = /#{Num}#{Unit}/ Chris@909: Chris@909: Comment = %r! /\* (?: .*? \*/ | .* ) !mx Chris@909: Function = /(?:url|alpha|attr|counters?)\((?:[^)\n\r\f]|\\\))*\)?/ Chris@909: Chris@909: Id = /##{Name}/ Chris@909: Class = /\.#{Name}/ Chris@909: PseudoClass = /:#{Name}/ Chris@909: AttributeSelector = /\[[^\]]*\]?/ Chris@909: end Chris@909: Chris@909: protected Chris@909: Chris@909: def scan_tokens encoder, options Chris@909: Chris@909: value_expected = nil Chris@909: states = [:initial] Chris@909: Chris@909: until eos? Chris@909: Chris@909: if match = scan(/\s+/) Chris@909: encoder.text_token match, :space Chris@909: Chris@909: elsif case states.last Chris@909: when :initial, :media Chris@909: if match = scan(/(?>#{RE::Ident})(?!\()|\*/ox) Chris@909: encoder.text_token match, :type Chris@909: next Chris@909: elsif match = scan(RE::Class) Chris@909: encoder.text_token match, :class Chris@909: next Chris@909: elsif match = scan(RE::Id) Chris@909: encoder.text_token match, :constant Chris@909: next Chris@909: elsif match = scan(RE::PseudoClass) Chris@909: encoder.text_token match, :pseudo_class Chris@909: next Chris@909: elsif match = scan(RE::AttributeSelector) Chris@909: # TODO: Improve highlighting inside of attribute selectors. Chris@909: encoder.text_token match[0,1], :operator Chris@909: encoder.text_token match[1..-2], :attribute_name if match.size > 2 Chris@909: encoder.text_token match[-1,1], :operator if match[-1] == ?] Chris@909: next Chris@909: elsif match = scan(/@media/) Chris@909: encoder.text_token match, :directive Chris@909: states.push :media_before_name Chris@909: next Chris@909: end Chris@909: Chris@909: when :block Chris@909: if match = scan(/(?>#{RE::Ident})(?!\()/ox) Chris@909: if value_expected Chris@909: encoder.text_token match, :value Chris@909: else Chris@909: encoder.text_token match, :key Chris@909: end Chris@909: next Chris@909: end Chris@909: Chris@909: when :media_before_name Chris@909: if match = scan(RE::Ident) Chris@909: encoder.text_token match, :type Chris@909: states[-1] = :media_after_name Chris@909: next Chris@909: end Chris@909: Chris@909: when :media_after_name Chris@909: if match = scan(/\{/) Chris@909: encoder.text_token match, :operator Chris@909: states[-1] = :media Chris@909: next Chris@909: end Chris@909: Chris@909: else Chris@909: #:nocov: Chris@909: raise_inspect 'Unknown state', encoder Chris@909: #:nocov: Chris@909: Chris@909: end Chris@909: Chris@909: elsif match = scan(/\/\*(?:.*?\*\/|\z)/m) Chris@909: encoder.text_token match, :comment Chris@909: Chris@909: elsif match = scan(/\{/) Chris@909: value_expected = false Chris@909: encoder.text_token match, :operator Chris@909: states.push :block Chris@909: Chris@909: elsif match = scan(/\}/) Chris@909: value_expected = false Chris@909: if states.last == :block || states.last == :media Chris@909: encoder.text_token match, :operator Chris@909: states.pop Chris@909: else Chris@909: encoder.text_token match, :error Chris@909: end Chris@909: Chris@909: elsif match = scan(/#{RE::String}/o) Chris@909: encoder.begin_group :string Chris@909: encoder.text_token match[0, 1], :delimiter Chris@909: encoder.text_token match[1..-2], :content if match.size > 2 Chris@909: encoder.text_token match[-1, 1], :delimiter if match.size >= 2 Chris@909: encoder.end_group :string Chris@909: Chris@909: elsif match = scan(/#{RE::Function}/o) Chris@909: encoder.begin_group :string Chris@909: start = match[/^\w+\(/] Chris@909: encoder.text_token start, :delimiter Chris@909: if match[-1] == ?) Chris@909: encoder.text_token match[start.size..-2], :content Chris@909: encoder.text_token ')', :delimiter Chris@909: else Chris@909: encoder.text_token match[start.size..-1], :content Chris@909: end Chris@909: encoder.end_group :string Chris@909: Chris@909: elsif match = scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox) Chris@909: encoder.text_token match, :float Chris@909: Chris@909: elsif match = scan(/#{RE::Color}/o) Chris@909: encoder.text_token match, :color Chris@909: Chris@909: elsif match = scan(/! *important/) Chris@909: encoder.text_token match, :important Chris@909: Chris@909: elsif match = scan(/(?:rgb|hsl)a?\([^()\n]*\)?/) Chris@909: encoder.text_token match, :color Chris@909: Chris@909: elsif match = scan(RE::AtKeyword) Chris@909: encoder.text_token match, :directive Chris@909: Chris@909: elsif match = scan(/ [+>:;,.=()\/] /x) Chris@909: if match == ':' Chris@909: value_expected = true Chris@909: elsif match == ';' Chris@909: value_expected = false Chris@909: end Chris@909: encoder.text_token match, :operator Chris@909: Chris@909: else Chris@909: encoder.text_token getch, :error Chris@909: Chris@909: end Chris@909: Chris@909: end Chris@909: Chris@909: encoder Chris@909: end Chris@909: Chris@909: end Chris@909: Chris@909: end Chris@909: end