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.
class Morpher
def initialize(key, name, attributes)
@key = key
@name = name
@attributes = attributes
@value = @attributes[@key]
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
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)
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 << "#{name}#{attributes}?>"
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)
"#{name}>"
end
def self.transform(source)
listener = Listener.new
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