require 'rexml/document' require 'rexml/streamlistener' require 'mega/dictionary' require 'nano/string/blank' require 'glue/html' module Nitro # :section: A collection of standard morphers. # The base morpher class. Morphers are triggered # by a special 'key' attribute in the xml stream and # transform the owner element. # # key = attribute 'key' # name = element name # attributes = alement attributes class Morpher def initialize(key, name, attributes, compiler = nil) @key = key @name = name @attributes = attributes @value = @attributes[@key] @compiler = compiler end def before_start(buffer); end def after_start(buffer); end def before_end(buffer); end def after_end(buffer); end end # A useful super class for morphers. class StandardMorpher < Morpher def after_end(buffer) # gmosx: leave the leading space. buffer << " " end end # attribute: times # #
  • ...
  • # # becomes # # #
  • ...
  • # class TimesMorpher < StandardMorpher def before_start(buffer) # gmosx: leave the trailing space. buffer << " " @attributes.delete(@key) end end # attribute: each, for # #
  • my item is #{item}
  • # # becomes # # #
  • my item is #{item}
  • # class EachMorpher < Morpher def before_start(buffer) if @value =~ / in / buffer << " " @attributes.delete(@key) end end def after_end(buffer) if @value =~ / in / buffer << " " end end end # attribute: if, unless # #
    @mycond is true
    # # becomes # # #
    @mycond is true
    # class IfMorpher < StandardMorpher def before_start(buffer) buffer << " " @attributes.delete(@key) end end # attribute: selected_if, checked_if, selected_unless, checked_unless # # # # becomes # # # # # # class SelectedIfMorpher < StandardMorpher def before_start(buffer) @attr, @cond = @key.split('_') @attributes.delete(@key) @attributes[@attr] = @attr buffer << " " end def after_start(buffer) @start_index = buffer.length end def before_end(buffer) @attributes.delete(@attr) @end_index = buffer.length buffer << Morphing.emit_end(@name) buffer << "" buffer << Morphing.emit_start(@name, @attributes) buffer << buffer[@start_index...@end_index] end end # :section: The morphing system. # A compiler module that translates xml stream. Multiple # 'key' attributes are supported per element. class Morphing #-- # The listener used to parse the xml stream. # # TODO: add support for morphing comments, text, etc. #++ class Listener # :nodoc: all include REXML::StreamListener # The compiler that calls this compiling stage. Useful # for advanced morphing effects and inter-stage # communication. attr_accessor :compiler attr_accessor :buffer def initialize super @buffer = '' @stack = [] end def tag_start(name, attributes) morphers = [] #for key, val in attributes # if morpher_class = Morphing.morphers[key] # morphers << morpher_class.new(key, name, attributes) # end #end Morphing.morphers.each do |key, morpher_class| if attributes.has_key? key morphers << morpher_class.new(key, name, attributes, compiler) end end morphers.each { |h| h.before_start(@buffer) } @buffer << Morphing.emit_start(name, attributes) morphers.each { |h| h.after_start(@buffer) } @stack.push(morphers) end def tag_end(name) morphers = @stack.pop morphers.reverse.each { |h| h.before_end(@buffer) } @buffer << Morphing.emit_end(name) morphers.reverse.each { |h| h.after_end(@buffer) } end def text(str) @buffer << str end def instruction(name, attributes) @buffer << "" end def comment(c) unless Template.strip_xml_comments @buffer << "" end end def doctype(name, pub_sys, long_name, uri) @buffer << "" end end def self.emit_start(name, attributes) attrs = attributes.map{ |k, v| %|#{k}="#{v}"| }.join(' ') attrs.blank? ? "<#{name}>" : "<#{name} #{attrs}>" end def self.emit_end(name) "" end def self.transform(source, compiler = nil) listener = Listener.new listener.compiler = compiler REXML::Document.parse_stream(source, listener) return listener.buffer end # The morphers map. @morphers = Dictionary.new class << self def morphers @morphers end def morphers=(hash) @morphers = hash end def add_morpher(key, klass) @morphers[key.to_s] = klass end alias_method :add, :add_morpher def delete_morpher(key) @morphers.delete(key) end alias_method :delete, :delete_morpher end # Install the default morphers. add_morpher :times, TimesMorpher add_morpher :if, IfMorpher add_morpher :unless, IfMorpher add_morpher :each, EachMorpher add_morpher :for, EachMorpher add_morpher :selected_if, SelectedIfMorpher add_morpher :selected_unless, SelectedIfMorpher add_morpher :checked_if, SelectedIfMorpher add_morpher :checked_unless, SelectedIfMorpher end end # * George Moschovitis # * Guillaume Pierronnet # * Chris Farmiloe