# Copyright (c) 2006 Michael Fellinger m.fellinger@gmail.com # All files in this distribution are subject to the terms of the Ruby license. require 'ramaze/template/ezamar/engine' # This applies a morphing-replace for the template. # # To use the functionality of Morpher you will need to have hpricot # installed, you will get one error in case you don't and the method # will be replaced by a stub that simply returns the template. # # The method first checks if you use any morphers and just skips # the step if you don't, this should give quite some speedup for # smaller templates that don't use this functionality at all. # the check works by searching the morphs with appended '=' # in the template. There may be a few cases where this won't work # since we cannot make any assumptions on the format. # # If you want to turn this functionality off, either remove Morpher # from: # Ramaze::Template::Ezamar::TRANSFORM_PIPELINE # or do: # Ramaze::Morpher.trait[:morphs] = {} # # The latter is a tad slower, but i mention the possibility in case you # find good use for it. # # You can add your own morphers in Ramaze::Morpher.trait[:morphs] # # For Example: # # Morpher.trait[:morphs]['if'] = '<?r %morph %expression ?>%content<?r end ?>' # # Now, assuming that some tag in your template is '<a if="@foo">x</a>' # # %morph stands for the name of your morph: 'if' # %expression is the stuff you write in the attribute: '@foo' # %content is the tag without the attribute (and all inside): '<a>x</a>' class Ezamar::Morpher # Use this trait to define your custom morphs. trait :morphs => { 'if' => '<?r %morph %expression ?>%content<?r end ?>', 'unless' => '<?r %morph %expression ?>%content<?r end ?>', 'for' => '<?r %morph %expression ?>%content<?r end ?>', 'each' => '<?r %expression.%morph do |_e| ?>%content<?r end ?>', 'times' => '<?r %expression.%morph do |_t| ?>%content<?r end ?>', } # Since the functionality is best explained by examples, here they come. # # Example: # # if: # <div if="@name">#@name</div> # morphs to: # <?r if @name ?> # <div>#@name</div> # <?r end ?> # # unless: # <div unless="@name">No Name</div> # morphs to: # <?r unless @name ?> # <div>No Name</div> # <?r end ?> # # for: # <div for="name in @names">#{name}</div> # morphs to: # <?r for name in @names ?> # <div>#{name}</div> # <?r end ?> # # times: # <div times="3">#{_t}<div> # morphs to: # <?r 3.times do |_t| ?> # <div>#{_t}</div> # <?r end ?> # # each: # <div each="[1,2,3]">#{_e}</div> # morphs to: # <?r [1,2,3].each do |_e| ?> # <div>#{_e}</div> # <?r end ?> # # The latter two examples show you also one standard introduced by a # limitation of the replacement-system. # # When you yield a value, please name it by the first character(s) of # the morphs name, with an underscore prefixed. # # for each an _e, for times a _t. # # This is by far not the best way to handle it and might lead to problems # due to the lack of proper scoping in ruby (if you define an _e or _t # before the block it will be overwritten). # # So please be careful, I tried to come up with something that is both easy # to write and doesn't look outright awful while keeping an easy to remember # mnemonic. # # TODO: # - Add pure Ruby implementation as a fall-back. def self.transform(template) morphs = trait[:morphs].map{|k,v| [k.to_s, v.to_s]}.select do |(k,v)| template.to_s.include?("#{k}=") end morphs = Hash[*morphs.flatten] return template if morphs.empty? require 'hpricot' hp = Hpricot(template) hp.each_child do |child| if child.elem? morphs.each_pair do |morph, replacement| if expression = child[morph] old = child.to_html child.remove_attribute(morph) replacement = replacement.dup. gsub('%morph', morph). gsub('%expression', expression). gsub('%content', child.to_html) template.gsub!(old, replacement) end end end end template rescue LoadError => ex error "Please install hpricot (for example via `gem install hpricot`) to get morphing" # replace this method with a stub that only returns the template. self.class_eval do def self.transform(template) template end end template end end