
# $Id: template.rb,v 1.4 2002/01/04 18:32:05 ikechin Exp $
# $Revision: 1.4 $

module HTML

  class Template
    def initialize(param = nil)
      @path = ['.','']
      @html
      file = nil
      if param.is_a?(Hash)
	file = param['filename']
	# allow Array and String
	@path.push(*param['path']) if param['path']
      elsif param.is_a?(String)
	file = param
      end
      if file then
	load(file)
      end
    end

    def load(file)
      @path.each do |path|
	if FileTest.exist?(filepath = File.join(path,file))
	  begin
	    fp = File.open(filepath,"r")
	    @html = fp.read
	    fp.close
	  rescue
	    raise IOError
	  end
	end
      end
      if @html
	parse
	return
      end
      raise IOError,"Template file #{file} not found"
    end

    def set_html(html)
      @html = html
      parse
    end

    attr_accessor :html
    attr_accessor :path

    ##
    # set params
    def param(key = {},*val)
      hash = if key.is_a?(String)
	       {key => val}
	     else
	       key
	     end
      hash.each do |k,v|
	if v.is_a?(Array)
	  v.each do |h|
	    node(k).param(h)
	  end
	elsif v.is_a?(Hash)
	  node(k).param(v)
	else
	  @html.gsub!(/<!var:#{k}>/i,escape_re(v.to_s))
	end
      end
    end

    ##
    # return HTML::TemplateNode object
    def node(name)
      if @node[name] != nil
	return @node[name]
      else
	raise ArgumentError,"node #{name} not found\n"
      end
    end

    ##
    # return result
    def output
      @html.gsub!(/<!var:[^>]+>/,"")
      @html.gsub!(/<!node:([^>]+)>/,"")
      ret_val = @html.dup
      # recycle the instance, but dirty way :-p
      set_html(@orig_text)
      return ret_val
    end

    ## aliases
    alias expand param
    alias loop node
    alias cond node
    alias to_s output

    private

    def escape_re(str)
      str.gsub(%r/\\/, "\\\\\\") 
    end
    ##
    # parse HTML documents
    def parse
      @node = {}
      # save original HTML
      @orig_text = @html.dup

      # support HTML comment style
      @html.gsub!(%r~<\!\-\-\s+(begin|end|var|include):([\w\./\-]+)\s+\-\->~,'<!\1:\2>')
      # include allow \w,/,- only
      @html.gsub!(%r~<!include:([\/\w\.\-]+)>~m) do 
	fp = nil
	@path.each do |path|
	  if FileTest.exist?(filepath = File.join(path,$1))
	    begin
	      fp = File.open(filepath,"r")
	    rescue
	      raise IOError
	    end
	    break if fp
	  end
	end
	begin
	  fp.read
	rescue NameError
	  raise IOError, "Include template #{$1} not found"
	end
      end

      # block allow \w,-,. only
      @html.gsub!(/<!begin:([\w\-\.]+)>(.*?)<!end:\1>/m) do
	name = $1
	body = $2
	@node[name] = HTML::TemplateNode.new(self,name,body)
	"<!node:#{name}>"
      end
    end
  end

  class TemplateNode < HTML::Template

    def initialize(parent,name,body)
      @parent = parent
      @name = name
      @html = body
      parse
      @original = @html.dup
    end

    attr_accessor :body

    def param(key = {},*val)
      hash = if key.is_a?(String)
	       {key => val}
	     else
	       key
	     end
      hash.each do |k,v|
	if v.is_a?(Array)
	  v.each do |h|
	    node(k).param(h)
	  end
	elsif v.is_a?(Hash)
	  node(k).param(v)
	else
	  @html.gsub!(/<!var:#{k}>/i,escape_re(v.to_s))
	end
      end
      html = @html.dup
      @parent.html.gsub!(/(<!node:#{@name}>)/) do 
	"#{html}#{$1}"
      end
      @html = @original.dup
    end

    alias expand param
    alias add param
    alias loop node
    alias cond node
  end
end


