grammar Rhtml rule doc space node space x:doc? { def convert if x.empty? node.convert else node.convert + x.convert end end } end rule node yield_with_name / yield / hprintlet / printlet / scriptlet / doctype / directive / self_closing_tag / imgtag / closetag / opentag / text end # Argh. For some reason I can't get this to work, so I split it into two rules # rule yield # '<%=' space 'yield' space (':' varname space)? '%>' { # def convert # var = "@content_for_" + varname.nil? ? "layout" : varname.text_value # line "rawtext #{var} # Note: you must define #{var} elsewhere" # end # } # end rule yield_with_name '<%=' space 'yield' space ':' varname space '%>' { def convert var = "@content_for_" + varname.text_value line "rawtext #{var} # Note: you must define #{var} elsewhere" end } end rule yield '<%=' space 'yield' space '%>' { def convert var = "@content_for_layout" line "rawtext #{var} # Note: you must define #{var} elsewhere" end } end rule scriptlet '<%' space code space '%>' { def convert text = code.text_value_removing_trims.strip if text =~ /\bdo( |.*|)?$/ line_in text elsif text == "end" line_out text else line text end end } end rule printlet '<%=' space code space '%>' { def convert line "rawtext #{code.convert_removing_trims}" end } end rule hprintlet '<%=' space 'h' ' '+ code space '%>' { def convert line "text #{code.convert_removing_trims}" end } end rule code (('%' !'>') / [^%])* { def convert_removing_trims convert.gsub(/\s*\-\s*$/, '') end def text_value_removing_trims text_value.gsub(/\s*\-\s*$/, '') end def convert code = text_value.strip # matches a word, followed by either a word, a string, or a symbol result = code.gsub(/^(\w+) ([\w:"'].*)$/, '\1(\2)') result end } end rule doctype ']* '>' { def convert line "rawtext '#{text_value}'" end } end rule directive ']* '>' { def convert line "rawtext '#{text_value}'" end } end rule tagname [A-Za-z0-9_:-]+ end rule varname [A-Za-z0-9_]+ end rule self_closing_tag '<' tag_name:tagname attrs:attributes? space '/>' { def convert line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}" end } end rule opentag '<' tag_name:tagname attrs:attributes? space '>' { def convert line_in "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert} do" end } end rule imgtag '<' tag_name:'img' attrs:attributes? space '>' { def convert line "#{tag_name.text_value}#{attrs.empty? ? "" : attrs.convert}" end } end rule closetag '' { def convert line_out "end" end } end rule text (([<>] !(tagname / [/%!])) / [^<>])+ { def convert stripped = text_value.strip if stripped.empty? "" else line "text '#{CGI.unescapeHTML(stripped).gsub(/\'/, "\\\\'") }'" end end } end rule attributes first:attribute rest:attributes* { def convert(internal = false) out = " " + first.convert + if rest.empty? "" else ",#{rest.elements.first.convert(true)}" # this is hacky -- is there a better way? end if (! internal) && out =~ /[\(\)]/ && out =~ /^(\s*)(.*?)(\s*)$/ out = "(#{$2})#{$3}" end out end } end rule attribute space n:(tagname) space '=' space v:quoted space { def convert attr_name = (n.text_value =~ /[-:]/) ? "'#{n.text_value}'" : ":#{n.text_value}" "#{attr_name} => #{v.convert}" end } end rule quoted (('"' val:([^"]*) '"') / ('\'' val:([^']*) '\'')) { def value val.text_value end def convert extract_erb(val.text_value) end def parenthesize_if_necessary(s) return s if s.strip =~ /^\(.*\)$/ || s =~ /^[A-Z0-9_]*$/i "(" + s + ")" end def escape_single_quotes(s) s.gsub(/[']/, '\\\\\'') end def escape_quoted(s) escape_single_quotes(CGI.unescapeHTML(s)) end def extract_erb(s, parenthesize = true) if s =~ /^(.*?)<%=(.*?)%>(.*?)$/ pre, code, post = escape_quoted($1), $2, escape_quoted($3) out = "" out = "'#{pre}' + " unless pre.length == 0 out += parenthesize_if_necessary(code.strip) unless post.length == 0 post = extract_erb(post, false) out += " + #{post}" end out = parenthesize_if_necessary(out) if parenthesize out else "'" + escape_quoted(s) + "'" end end } end rule space [ \n\t]* end end