require 'rexml/document' require 'rexml/streamlistener' require 'facet/class/by_name' require 'nitro/element' require 'glue/html' module Nitro # A compiler that handles the processing of Elements class Elements # :nodoc: all class Listener # :nodoc: all include REXML::StreamListener attr_accessor :buffer attr_accessor :stack def initialize(compiler) super() @compiler = compiler @buffer = '' @stack = [] end PREFIX_RE = /^#{Element.prefix}:/ CAPITALIZED_RE = /^[A-Z]/ def tag_start(name, attributes) # check if the name starts with the element prefix, or # is capitalized. if klass = is_element?(name) obj = klass.new attributes.each do | k, v | obj.instance_variable_set("@#{k}", v) end @stack.push [obj, @buffer, @parent] @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) # check if the name starts with the element prefix, or # is capitalized. if is_element? name obj, @buffer, @parent = @stack.pop @buffer << obj.render else @buffer << "" end end # Check if a tag is a Nitro::Element. If found, it also # tries to auto-extend the klass. Returns the Element class # if found. def is_element?(name) # Doesn't support modulized classes # name = name.demodulize return false unless name =~ PREFIX_RE or name =~ CAPITALIZED_RE name = name.gsub(PREFIX_RE,'').camelize if name =~ PREFIX_RE # Try to use Controller::xxx #-- # gmosx, THINK: this looks a bit dangerous to me! #++ begin #-- # gmosx, FIXME: Class.by_name also returns top level # classes, how can we fix this? #++ klass = Class.by_name("#{@compiler.controller}::#{name}") rescue # drink it! end # Try to use the Controller's :elements annotation if (!klass) and namespace = @compiler.controller.ann.self[:element_namespace] begin klass = Class.by_name("#{namespace}::#{name}") rescue # drink it! end end # Try to use Nitro::Element::xxx then ::xxx begin klass = Class.by_name("Nitro::Element::#{name}") rescue # drink it! end unless klass return false unless klass.kind_of? Class # Try to auto-extend. unless klass.ancestors.include? Nitro::Element if Element.auto_extend klass.send(:include, Nitro::ElementMixin) else return false end end return klass end def text(str) @buffer << str end def instruction(name, attributes) @buffer << "" end def comment(c) unless Template.strip_xml_comments @buffer << "" end end end class << self def parse(source) self.new.parse(source) end def transform(source, compiler) self.new.transform(source, compiler) end end # Expand the elemens found in source. #-- # gmosx, FIXME: optimize this, how? # gmosx, FIXME: this is a hack fix, improve. # TODO:farms why is cleanup called this many times?!?!? ... waste of gsubs #++ def transform(source, compiler) listener = Listener.new(compiler) REXML::Document.parse_stream(source, listener) return listener.buffer end end unless const_defined? :ElementCompiler ElementCompiler = Elements end end