require 'strscan' module Web module CSS class Scanner H = "[0-9a-f]" NONASCII = "[\302\240-\364\217\277\277]" UNICODE = "\\\\#{H}{1,6}[ \\t\\r\\n\\f]?" ESCAPE = "(?:#{UNICODE}|\\\\[ -~\302\240-\364\217\277\277])" NMSTART = "(?:[a-z]|#{NONASCII}|#{ESCAPE})" NMCHAR = "(?:[a-z0-9\-]|#{NONASCII}|#{ESCAPE})" NL = "(?:\\n|\\r\\n|\\r|\\f)" STRING1 = "(?:\"([\\t !\#$%&(-~]|#{NL}|\'|#{NONASCII}|#{ESCAPE})*\")" STRING2 = "(?:\'([\\t !\#$%&(-~]|#{NL}|\"|#{NONASCII}|#{ESCAPE})*\')" IDENT = "#{NMSTART}#{NMCHAR}*" NAME = "#{NMCHAR}+" NUM = "(?:[0-9]+|[0-9]*\.[0-9]+)" STRING = "(?:#{STRING1}|#{STRING2})" URL = "(?:[!\#$%&*-~]|#{NONASCII}|#{ESCAPE})*" W = "[ \\t\\r\\n\\f]*" RANGE = "\\?{1,6}|#{H}(\\?{0,5}|#{H}(\\?{0,4}|#{H}(\\?{0,3}|#{H}(\\?{0,2}|#{H}(\\?\\?|#{H})))))" def initialize(io,rs=$/) @__io = io # IO @__rs = rs # Record Separator @__scan = StringScanner.new('') # StringScanner(strscan) @__line_no = 0 # Line Number @__queue = [] # Token Queue @__state = :default # Scanner State @__state_stack = [] # State Stack(CALL/RETURN) init if respond_to? :init end def rest? if @__io.eof? @__scan.rest? else true end end # def shift until token = @__queue.shift return nil unless rest? if @__scan.empty? @__line_no += 1 @__scan.string = @__io.gets(@__rs) end @__pos = @__scan.pointer send @__state end return token end def unshift(token) @__queue.unshift token end def push(token) @__queue.push token end def pop @__queue.pop end def token shift end def call_state(state) @__state_stack.push @__state @__state = state end def return_state @__state = @__state_stack.pop end def transit_state(state) @__state = state end def scan(regex) @__scan.scan(regex) end def scan_until(regex) @__scan.scan_until(regex) end def matched @__scan.matched end def unscan @__scan.unscan end def ungets(str) @__scan.string = str + @__scan.rest end def [](n) @__scan[n] end def aref(n) @__scan[n] end def pos @__pos end def line_no @__line_no end # def default if scan(/[ \t\r\n\f]+/ui) push [:S,matched] elsif scan(/\/\*/ui) transit_state :comment elsif scan(//ui) push [:CDC,matched] elsif scan(/~=/ui) push [:INCLUDES,matched] elsif scan(/\|=/ui) push [:DASHMATCH,matched] elsif scan(/#{STRING}/ui) push [:STRING,matched] elsif scan(/url\(#{W}(#{STRING})#{W}\)/ui) push [:URI,matched] elsif scan(/url\(#{W}(#{URL})#{W}\)/ui) push [:URI,matched] elsif scan(/#{IDENT}/ui) push [:IDENT,matched] elsif scan(/\##{NAME}/ui) push [:HASH,matched] elsif scan(/@#{IDENT}/ui) case matched.downcase when "@import" push [:IMPORT_SYM,matched] when "@page" push [:PAGE_SYM,matched] when "@media" push [:MEDIA_SYM,matched] when "@font-face" push [:FONT_FACE_SYM,matched] when "@charset" push [:CHARSET_SYM,matched] else push [:ATKEYWORD,matched] end elsif scan(/!#{W}important/ui) push [:IMPORTANT_SYM,matched] elsif scan(/(#{NUM})em/ui) push [:EMS,matched] elsif scan(/(#{NUM})ex/ui) push [:EXS,matched] elsif scan(/(#{NUM})px/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})cm/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})mm/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})in/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})pt/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})pc/ui) push [:LENGTH,matched] elsif scan(/(#{NUM})deg/ui) push [:ANGLE,matched] elsif scan(/(#{NUM})rad/ui) push [:ANGLE,matched] elsif scan(/(#{NUM})grad/ui) push [:ANGLE,matched] elsif scan(/(#{NUM})ms/ui) push [:TIME,matched] elsif scan(/(#{NUM})s/ui) push [:TIME,matched] elsif scan(/(#{NUM})Hz/ui) push [:FREQ,matched] elsif scan(/(#{NUM})kHz/ui) push [:FREQ,matched] elsif scan(/(#{NUM})(#{IDENT})/ui) push [:DIMEN,matched] elsif scan(/(#{NUM})%/ui) push [:PERCENTAGE,matched] elsif scan(/(#{NUM})/ui) push [:NUMBER,matched] elsif scan(/(#{IDENT})\(/ui) push [:FUNCTION,matched] elsif scan(/U\+#{RANGE}/ui) push [:UNICODERANGE,matched] elsif scan(/U\+#{H}{1,6}-#{H}{1,6}/ui) push [:UNICODERANGE,matched] elsif scan(/#{IDENT}/ui) push [:IDENT,matched] elsif scan(/./ui) push [matched.intern,matched] else raise end end def comment if scan(/[^*]/ui) # elsif scan(/\*\//ui) transit_state :default elsif scan(/./ui) # else raise end end # end # Scanner end # CSS end # Web if $0==__FILE__ scan = Web::CSS::Scanner.new(ARGF) while token=scan.token p token end end