summaryrefslogtreecommitdiff
path: root/lib/coderay/scanners/css.rb
diff options
context:
space:
mode:
authormurphy <murphy@rubychan.de>2008-09-18 01:12:44 +0000
committermurphy <murphy@rubychan.de>2008-09-18 01:12:44 +0000
commit41acfacb91970c8fa4e8b34f35c718eb329a3733 (patch)
treef11afbc209873285165934749e47b74e6936f25f /lib/coderay/scanners/css.rb
parente46b6cbe74db250d743e7f194bfc7514529a82cc (diff)
downloadcoderay-41acfacb91970c8fa4e8b34f35c718eb329a3733.tar.gz
New: *CSS Scanner* (closes #29).
* Based on Stefan Walk's implementation, with fixes, enhancements and speedups. * It seems to be fairly fast and highlights nicely. * I added the styles for Ignis Draconis, S5, and YUI as example code for testing. More changes: * Added three new token classes, :important, :pseudo_class, and :value, along with CSS styles.
Diffstat (limited to 'lib/coderay/scanners/css.rb')
-rw-r--r--lib/coderay/scanners/css.rb181
1 files changed, 181 insertions, 0 deletions
diff --git a/lib/coderay/scanners/css.rb b/lib/coderay/scanners/css.rb
new file mode 100644
index 0000000..897e27a
--- /dev/null
+++ b/lib/coderay/scanners/css.rb
@@ -0,0 +1,181 @@
+module CodeRay
+module Scanners
+
+ class CSS < Scanner
+
+ register_for :css
+
+ module RE
+ NonASCII = /[\x80-\xFF]/
+ 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]|#{NonASCII}|#{Escape}/
+ NMStart = /[_a-zA-Z]|#{NonASCII}|#{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)\((?:[^)\n\r\f]|\\\))*\)?/
+
+ Id = /##{Name}/
+ Class = /\.#{Name}/
+ PseudoClass = /:#{Name}/
+
+ end
+
+ def scan_tokens tokens, options
+
+ value_expected = nil
+ states = [:initial]
+
+ until eos?
+
+ kind = nil
+ match = nil
+
+ if scan(/\s+/)
+ kind = :space
+
+ elsif case states.last
+ when :initial
+ if scan(/#{RE::Ident}|\*/ox)
+ kind = :keyword
+ elsif scan RE::Class
+ kind = :class
+ elsif scan RE::Id
+ kind = :constant
+ elsif scan RE::PseudoClass
+ kind = :pseudo_class
+ elsif scan RE::Name
+ kind = :identifier
+ end
+
+ when :block
+ if scan(/(?>#{RE::Ident})(?!\()/ox)
+ if value_expected
+ kind = :value
+ else
+ kind = :key
+ end
+ end
+
+ when :comment
+ if scan(/(?:[^*\s]|\*(?!\/))+/)
+ kind = :comment
+ elsif scan(/\*\//)
+ kind = :comment
+ states.pop
+ elsif scan(/\s+/)
+ kind = :space
+ end
+
+ else
+ raise_inspect 'Unknown state', tokens
+
+ end
+
+ elsif scan(/\/\*/)
+ kind = :comment
+ states.push :comment
+
+ elsif scan(/\{/)
+ value_expected = false
+ kind = :operator
+ states.push :block
+
+ elsif scan(/\}/)
+ value_expected = false
+ if states.last == :block
+ kind = :operator
+ states.pop
+ else
+ kind = :error
+ end
+
+ elsif match = scan(/#{RE::String}/o)
+ tokens << [:open, :string]
+ tokens << [match[0, 1], :delimiter]
+ tokens << [match[1..-2], :content] if match.size > 2
+ tokens << [match[-1, 1], :delimiter] if match.size >= 2
+ tokens << [:close, :string]
+ next
+
+ elsif match = scan(/#{RE::Function}/o)
+ tokens << [:open, :string]
+ start = match[/^\w+\(/]
+ tokens << [start, :delimiter]
+ if match[-1] == ?)
+ tokens << [match[start.size..-2], :content]
+ tokens << [')', :delimiter]
+ else
+ tokens << [match[start.size..-1], :content]
+ end
+ tokens << [:close, :string]
+ next
+
+ elsif scan(/(?: #{RE::Dimension} | #{RE::Percentage} | #{RE::Num} )/ox)
+ kind = :float
+
+ elsif scan(/#{RE::Color}/o)
+ kind = :color
+
+ elsif scan(/! *important/)
+ kind = :important
+
+ elsif scan(/rgb\([^()\n]*\)?/)
+ kind = :color
+
+ elsif scan(/#{RE::AtKeyword}/o)
+ kind = :directive
+
+ elsif match = scan(/ [+>:;,.=()\/] /x)
+ if match == ':'
+ value_expected = true
+ elsif match == ';'
+ value_expected = false
+ end
+ kind = :operator
+
+ else
+ getch
+ kind = :error
+
+ end
+
+ match ||= matched
+ if $DEBUG and not kind
+ raise_inspect 'Error token %p in line %d' %
+ [[match, kind], line], tokens
+ end
+ raise_inspect 'Empty token', tokens unless match
+
+ tokens << [match, kind]
+ # tokens << [states.inspect, :error]
+
+ end
+
+ tokens
+ end
+
+ end
+
+end
+end