require 'rexml/document' require 'rexml/streamlistener' require 'facet/string/capitalized%3F' module Nitro # A programmatically generated element. # # === Design # # An underscore is used for the standard attibutes to # avoid name clashes. #-- # TODO: # * separate 'view' template files. #++ class Element # The parent of this element. attr_accessor :_parent # The childrens of this element. attr_accessor :_children # The text of this element. attr_accessor :_text # The view of this element. attr_accessor :_view def initialize(*args) @_children = [] @_text = '' end def open end def content # "#{@_text}#{render_children}" @_text end def close end def render "#{open}#{content}#{close}" end def render_children str = '' for c in @_children str << c.render end return str end def add_child(child) child._parent = self @_children << child end end # Processes a page containing elements. class ElementProcessor # :nodoc: all class Listener # :nodoc: all include REXML::StreamListener attr_accessor :buffer attr_accessor :stack def initialize super @buffer = '' @stack = [] end def tag_start(name, attributes) if name =~ /^[A-Z]/ # .capitalized? obj = Object.const_get(name).new attributes.each do | k, v | obj.instance_variable_set("@#{k}", v) end @stack.push [obj, @buffer] @buffer = obj._text @parent.add_child(obj) if @parent @parent = obj else # This is a static element. attrs = [] attributes.each do | k, v | attrs << %|#{k}="#{v}"| end attrs = attrs.empty? ? nil : " #{attrs.join(' ')}" @buffer << "<#{name}#{attrs}>" end end def tag_end(name) if name =~ /^[A-Z]/ # .capitalized? obj, @buffer = @stack.pop @buffer << obj.render else @buffer << "" end end def text(str) @buffer << str end def instruction(name, attributes) @buffer << "" end end class << self def parse(source) self.new.parse(source) end def render(source) self.new.render(source) end end # Expand the elemens found in source. def render(source) listener = Listener.new REXML::Document.parse_stream(source, listener) # gmosx, FIXME: optimize this, how? listener.buffer.gsub! /<(.*) ([^>]*)><\/\1>/, '<\1 \2 />' return listener.buffer end end end