class HTree::TemplateCompiler
Constants
- ID_PAT
- IGNORABLE_ELEMENTS
- NAME_FARGS_PAT
- WhiteSpacePreservingElements
Public Class Methods
new()
click to toggle source
# File htree/template.rb, line 443 def initialize @gensym_id = 0 end
Public Instance Methods
check_syntax(code)
click to toggle source
# File htree/template.rb, line 709 def check_syntax(code) unless valid_syntax?(code) raise HTree::Error, "invalid ruby code: #{code}" end end
compile_body(outvar, contextvar, node, is_toplevel, local_templates={})
click to toggle source
# File htree/template.rb, line 614 def compile_body(outvar, contextvar, node, is_toplevel, local_templates={}) if node.elem? && IGNORABLE_ELEMENTS[node.name] && node.attributes.empty? node = TemplateNode.new(node.children) else node = TemplateNode.new(node) end generate_logic_node([:content], node, local_templates).generate_xml_output_code(outvar, contextvar) end
compile_call(ignore_tag, spec)
click to toggle source
# File htree/template.rb, line 760 def compile_call(ignore_tag, spec) # spec : [recv.]meth[(args)] spec = spec.strip spec, args = split_args(spec) unless /#{ID_PAT}\z/o =~ spec raise HTree::Error, "invalid _call: #{spec}" end meth = $& spec = $` if /\A\s*\z/ =~ spec recv = nil elsif /\A\s*(.*)\.\z/ =~ spec recv = $1 else raise HTree::Error, "invalid _call: #{spec}" end if recv check_syntax(recv) check_syntax("#{recv}.#{meth}(#{args})") end check_syntax("#{meth}(#{args})") [:call, recv, meth, args] end
compile_dynamic_text(ignore_tag, expr)
click to toggle source
# File htree/template.rb, line 715 def compile_dynamic_text(ignore_tag, expr) check_syntax(expr) logic = [:text, expr] logic = [:tag, logic] unless ignore_tag logic end
compile_dynamic_tree(ignore_tag, expr)
click to toggle source
# File htree/template.rb, line 722 def compile_dynamic_tree(ignore_tag, expr) check_syntax(expr) logic = [:tree, expr] logic = [:tag, logic] unless ignore_tag logic end
compile_global_template(name_fargs, node)
click to toggle source
# File htree/template.rb, line 568 def compile_global_template(name_fargs, node) unless /\A#{NAME_FARGS_PAT}\z/o =~ name_fargs raise HTree::Error, "invalid template declaration: #{name_fargs}" end name = $1 fargs = $2 ? $2.scan(ID_PAT) : [] outvar = gensym('out') contextvar = gensym('top_context') args2 = [outvar, contextvar, *fargs] <<"End" def #{name}(#{fargs.join(',')}) HTree.parse(_xml_#{name}(#{fargs.join(',')})) end def _xml_#{name}(#{fargs.join(',')}) #{outvar} = HTree::Encoder.new(HTree::Encoder.internal_charset) #{contextvar} = HTree::DefaultContext _ht_#{name}(#{args2.join(',')}) #{outvar}.finish end def _ht_#{name}(#{args2.join(',')}) #{compile_body(outvar, contextvar, node, false)}\ end public :_ht_#{name} End end
compile_if(ignore_tag, expr, else_call)
click to toggle source
# File htree/template.rb, line 729 def compile_if(ignore_tag, expr, else_call) check_syntax(expr) then_logic = [:content] unless ignore_tag then_logic = [:tag, then_logic] end else_logic = nil if else_call else_logic = compile_call(true, else_call) end [:if, expr, then_logic, else_logic] end
compile_iter(ignore_tag, spec)
click to toggle source
# File htree/template.rb, line 784 def compile_iter(ignore_tag, spec) # spec: <n _iter="expr.meth[(args)]//fargs" >...</n> spec = spec.strip unless %r{\s*//\s*(#{ID_PAT}\s*(?:,\s*#{ID_PAT}\s*)*)?\z}o =~ spec raise HTree::Error, "invalid block arguments for _iter: #{spec}" end call = $`.strip fargs = $1 ? $1.strip : '' check_syntax("#{call} {|#{fargs}| }") logic = [:content] unless ignore_tag logic = [:tag, logic] end [:iter, call, fargs, logic] end
compile_iter_content(ignore_tag, spec)
click to toggle source
# File htree/template.rb, line 800 def compile_iter_content(ignore_tag, spec) # spec: <n _iter_content="expr.meth[(args)]//fargs" >...</n> spec = spec.strip unless %r{\s*//\s*(#{ID_PAT}\s*(?:,\s*#{ID_PAT}\s*)*)?\z}o =~ spec raise HTree::Error, "invalid block arguments for _iter: #{spec}" end call = $`.strip fargs = $1 ? $1.strip : '' check_syntax("#{call} {|#{fargs}| }") logic = [:content] logic = [:iter, call, fargs, logic] unless ignore_tag logic = [:tag, logic] end logic end
compile_local_template(name_fargs, node, local_templates)
click to toggle source
# File htree/template.rb, line 596 def compile_local_template(name_fargs, node, local_templates) unless /\A#{NAME_FARGS_PAT}\z/o =~ name_fargs raise HTree::Error, "invalid template declaration: #{name_fargs}" end name = $1 fargs = $2 ? $2.scan(ID_PAT) : [] outvar = gensym('out') contextvar = gensym('top_context') args2 = [outvar, contextvar, *fargs] <<"End" #{name} = lambda {|#{args2.join(',')}| #{compile_body(outvar, contextvar, node, false, local_templates)}\ } End end
compile_node(node, local_templates)
click to toggle source
# File htree/template.rb, line 623 def compile_node(node, local_templates) case node when HTree::Doc TemplateNode.new(node.children.map {|n| compile_node(n, local_templates) }) when HTree::Elem ht_attrs = node.attributes.find_all {|name, text| template_attribute? name } ht_attrs = ht_attrs.sort_by {|htname, text| htname.universal_name } ignore_tag = false unless ht_attrs.empty? attr_mod = {} ht_attrs.each {|htname, text| attr_mod[htname] = nil if /\A_attr_/ =~ htname.local_name attr_mod[TemplateAttrName.new(htname.namespace_prefix, htname.namespace_uri, $')] = text end } ht_attrs.reject! {|htname, text| /\A_attr_/ =~ htname.local_name } node = node.subst_subnode(attr_mod) ignore_tag = IGNORABLE_ELEMENTS[node.name] && node.attributes.empty? end ht_names = ht_attrs.map {|htname, text| htname.universal_name } ht_vals = ht_attrs.map {|htname, text| text.to_s } case ht_names when [] generate_logic_node([:tag, [:content]], node, local_templates) when ['_text'] # <n _text="expr" /> or <n _text>expr</n> if ht_vals[0] != '_text' # xxx: attribute value is really omitted? expr = ht_vals[0] else children = node.children if children.length != 1 raise HTree::Error, "_text expression has #{children.length} nodes" end if !children[0].text? raise HTree::Error, "_text expression is not text: #{children[0].class}" end expr = children[0].to_s end if ignore_tag && /\A\s*'((?:[^'\]|\.)*)'\s*\z/m =~ expr # if expr is just a constant string literal, use it as a literal text. # This saves dynamic evaluation of <span _text="' '"/> # xxx: handle "..." as well if it has no #{}. HTree::Text.new($1.gsub(/\(.)/m, '\1')) else generate_logic_node(compile_dynamic_text(ignore_tag, expr), node, local_templates) end when ['_tree'] # <n _tree="expr" /> or <n _tree>expr</n> if ht_vals[0] != '_tree' # xxx: attribute value is really omitted? expr = ht_vals[0] else children = node.children if children.length != 1 raise HTree::Error, "_tree expression has #{children.length} nodes" end if !children[0].text? raise HTree::Error, "_tree expression is not text: #{children[0].class}" end expr = children[0].to_s end generate_logic_node(compile_dynamic_tree(ignore_tag, expr), node, local_templates) when ['_if'] # <n _if="expr" >...</n> generate_logic_node(compile_if(ignore_tag, ht_vals[0], nil), node, local_templates) when ['_else', '_if'] # <n _if="expr" _else="expr.meth(args)" >...</n> generate_logic_node(compile_if(ignore_tag, ht_vals[1], ht_vals[0]), node, local_templates) when ['_call'] # <n _call="recv.meth(args)" /> generate_logic_node(compile_call(ignore_tag, ht_vals[0]), node, local_templates) when ['_iter'] # <n _iter="expr.meth(args)//fargs" >...</n> generate_logic_node(compile_iter(ignore_tag, ht_vals[0]), node, local_templates) when ['_iter_content'] # <n _iter_content="expr.meth(args)//fargs" >...</n> generate_logic_node(compile_iter_content(ignore_tag, ht_vals[0]), node, local_templates) else raise HTree::Error, "unexpected template attributes: #{ht_attrs.inspect}" end else return node end end
compile_template(src)
click to toggle source
# File htree/template.rb, line 508 def compile_template(src) srcdoc = parse_template(src) templates = [] extract_templates(srcdoc, templates, true) methods = [] templates.each {|name_args, node| methods << compile_global_template(name_args, node) } <<"End" require 'htree/encoder' require 'htree/context' Module.new.module_eval <<'EE' module_function #{methods.join('').chomp} self EE End end
expand_template(template, out, encoding, binding)
click to toggle source
# File htree/template.rb, line 490 def expand_template(template, out, encoding, binding) template = parse_template(template) is_html = template_is_html(template) outvar = gensym('out') contextvar = gensym('top_context') code = '' code << "#{outvar} = HTree::Encoder.new(#{encoding.dump})\n" code << "#{outvar}.html_output = true\n" if is_html code << "#{contextvar} = #{is_html ? "HTree::HTMLContext" : "HTree::DefaultContext"}\n" code << compile_body(outvar, contextvar, template, false) code << "[#{outvar}.#{is_html ? "finish" : "finish_with_xmldecl"}, #{outvar}.minimal_charset]\n" #puts code; STDOUT.flush result, minimal_charset = eval(code, binding, "(eval:#{__FILE__}:#{__LINE__})") out.charset = minimal_charset if out.respond_to? :charset= out << result out end
extract_templates(node, templates, is_toplevel)
click to toggle source
# File htree/template.rb, line 531 def extract_templates(node, templates, is_toplevel) case node when HTree::Doc subst = {} node.children.each_with_index {|n, i| subst[i] = extract_templates(n, templates, is_toplevel) } node.subst_subnode(subst) when HTree::Elem ht_attrs, = node.attributes.partition {|name, text| template_attribute? name } if ht_attrs.empty? subst = {} node.children.each_with_index {|n, i| subst[i] = extract_templates(n, templates, is_toplevel) } node.subst_subnode(subst) else ht_attrs.each {|htname, text| if htname.universal_name == '_template' name_fargs = text.to_s templates << [name_fargs, node.subst_subnode('_template' => nil)] return nil end } if is_toplevel raise HTree::Error, "unexpected template attributes in toplevel: #{ht_attrs.inspect}" else node end end else node end end
generate_logic_node(logic, node, local_templates)
click to toggle source
# File htree/template.rb, line 817 def generate_logic_node(logic, node, local_templates) # logic ::= [:if, expr, then_logic, else_logic] # | [:iter, call, fargs, logic] # | [:tag, logic] # | [:text, expr] # | [:tree, expr] # | [:call, expr, meth, args] # | [:content] # | [:empty] case logic.first when :empty nil when :content subtemplates = [] children = [] node.children.each {|c| children << extract_templates(c, subtemplates, false) } if subtemplates.empty? TemplateNode.new(node.children.map {|n| compile_node(n, local_templates) }) else local_templates = local_templates.dup decl = '' subtemplates.each {|sub_name_args, sub_node| sub_name = sub_name_args[ID_PAT] local_templates[sub_name] = sub_name decl << "#{sub_name} = " } decl << "nil\n" defs = [] subtemplates.each {|sub_name_args, sub_node| defs << lambda {|out, context| out.output_logic_line compile_local_template(sub_name_args, sub_node, local_templates) } } TemplateNode.new( lambda {|out, context| out.output_logic_line decl }, defs, children.map {|n| compile_node(n, local_templates) } ) end when :text _, expr = logic TemplateNode.new(lambda {|out, context| out.output_dynamic_text expr }) when :tree _, expr = logic TemplateNode.new(lambda {|out, context| out.output_dynamic_tree expr, make_context_expr(out, context) }) when :tag _, rest_logic = logic if rest_logic == [:content] && node.empty_element? node else subst = {} node.children.each_index {|i| subst[i] = nil } subst[0] = TemplateNode.new(generate_logic_node(rest_logic, node, local_templates)) node.subst_subnode(subst) end when :if _, expr, then_logic, else_logic = logic children = [ lambda {|out, context| out.output_logic_line "if (#{expr})" }, generate_logic_node(then_logic, node, local_templates) ] if else_logic children.concat [ lambda {|out, context| out.output_logic_line "else" }, generate_logic_node(else_logic, node, local_templates) ] end children << lambda {|out, context| out.output_logic_line "end" } TemplateNode.new(*children) when :iter _, call, fargs, rest_logic = logic TemplateNode.new( lambda {|out, context| out.output_logic_line "#{call} {|#{fargs}|" }, generate_logic_node(rest_logic, node, local_templates), lambda {|out, context| out.output_logic_line "}" } ) when :call _, recv, meth, args = logic TemplateNode.new( lambda {|out, context| as = [out.outvar, ", ", make_context_expr(out, context)] unless args.empty? as << ", " << args end if recv out.output_logic_line "(#{recv})._ht_#{meth}(#{as.join('')})" elsif local_templates.include? meth out.output_logic_line "#{meth}.call(#{as.join('')})" else out.output_logic_line "_ht_#{meth}(#{as.join('')})" end } ) else raise Exception, "[bug] invalid logic: #{logic.inspect}" end end
gensym(suffix='')
click to toggle source
# File htree/template.rb, line 447 def gensym(suffix='') @gensym_id += 1 "g#{@gensym_id}#{suffix}" end
make_context_expr(out, context)
click to toggle source
# File htree/template.rb, line 920 def make_context_expr(out, context) ns = context.namespaces.reject {|k, v| HTree::Context::DefaultNamespaces[k] == v } if ns.empty? result = out.contextvar else result = "#{out.contextvar}.subst_namespaces(" sep = '' ns.each {|k, v| result << sep << (k ? k.dump : "nil") << '=>' << v.dump sep = ', ' } result << ")" end result end
parse_template(template)
click to toggle source
# File htree/template.rb, line 452 def parse_template(template) strip_whitespaces(HTree.parse(template)) end
split_args(spec)
click to toggle source
# File htree/template.rb, line 742 def split_args(spec) return spec, '' if /\)\z/ !~ spec i = spec.length - 1 nest = 0 begin raise HTree::Error, "unmatched paren: #{spec}" if i < 0 case spec[i] when ?\) nest += 1 when ?\( nest -= 1 end i -= 1 end while nest != 0 i += 1 return spec[0, i], spec[(i+1)...-1] end
strip_whitespaces(template)
click to toggle source
# File htree/template.rb, line 460 def strip_whitespaces(template) case template when HTree::Doc HTree::Doc.new(*template.children.map {|c| strip_whitespaces(c) }.compact) when HTree::Elem, HTree::Doc return template if WhiteSpacePreservingElements[template.name] subst = {} template.children.each_with_index {|c, i| subst[i] = strip_whitespaces(c) } template.subst_subnode(subst) when HTree::Text if /\A[ \t\r\n]*\z/ =~ template.rcdata nil else template end else template end end
template_attribute?(name)
click to toggle source
# File htree/template.rb, line 527 def template_attribute?(name) /\A_/ =~ name.local_name end
template_is_html(template)
click to toggle source
# File htree/template.rb, line 482 def template_is_html(template) template.each_child {|c| return false if c.xmldecl? return true if c.elem? && c.element_name.namespace_uri == 'http://www.w3.org/1999/xhtml' } false end
valid_syntax?(code)
click to toggle source
# File htree/template.rb, line 701 def valid_syntax?(code) begin eval("BEGIN {return true}\n#{code.untaint}") rescue SyntaxError raise SyntaxError, "invalid code: #{code}" end end