class Randy RENDERERS = {} def self.add_renderer(klass, &block) RENDERERS[klass] = block end def self.renderable?(v) v.respond_to?(:render) || RENDERERS.include?(v.class) end def self.render(object, ctx=nil, out='') if object.respond_to? :render object.render(out, ctx) elsif RENDERERS.include?(object.class) RENDERERS[object.class].call(object, out, ctx) else raise ArgumentError, "can't render #{object.class}" end out end end Randy.add_renderer(String) { |string, out, ctx| out << string.gsub(/&/n, '&'). gsub(/\"/n, '"'). gsub(/>/n, '>'). gsub(/" end end class Element def initialize(name, attrs={}, children=[]) @name, @attrs, @children = name.to_sym, attrs, children end attr_accessor :name attr_reader :attrs attr_reader :children def ==(other) name == other.name && attrs == other.attrs && children == other.children end def [](*vs) vs.each { |v| if Hash === v v.each { |name, value| attrs[name.to_sym] = value } elsif Randy.renderable?(v) children << v else raise ArgumentError, "don't know what to do with #{v.class}" end } self end def render(out, ctx) case attrs[:render] when Method, Proc Randy.render(attrs[:render][self], ctx, out) when String, Symbol r = attrs.delete(:render).to_sym data = ctxget(ctx, attrs.delete(:data)) if attrs.include?(:data) if ctx.respond_to?(m="render_#{r}") Randy.render(ctx.__send__(m, self), ctx, out) elsif Randy::RENDERERS.include?(r) Randy.render(Randy::RENDERERS[r][self, data], ctx, out) else raise NameError, "don't know about the renderer #{attrs[:render]}" end when nil if attrs.include?(:data) attrs[:render] = :data render(out, ctx) else render_tag(ctx, out) end end end def render_tag(ctx, out) out << "<#{name}" attrs.each { |k,v| out << ' ' Randy.render(k.to_s, ctx, out) out << '="' Randy.render(v, ctx, out) out << '"' } if children.empty? out << " />" else out << ">" children.each { |c| Randy.render(c, ctx, out) } out << "" end end def pattern(name, all=false) if attrs[:pattern] && attrs[:pattern].to_sym == name pat = deepdup pat.attrs.delete :pattern pat else if all children.map { |c| Randy::Element === c ? c.pattern(name, all) : nil }. flatten.compact else children.each { |c| if Randy::Element === c && pat = c.pattern(name) return pat end } nil end end end def fill_slot(name, value) if attrs[:slot] && attrs[:slot].to_sym == name attrs.delete :slot attrs[:render] = :data attrs[:data] = value end children.each { |c| if Randy::Element === c c.fill_slot(name, value) end } self end def inspect r = name.inspect r << "[%s]" % attrs.inspect[1...-1] unless attrs.empty? r << children.inspect unless children.empty? && !attrs.empty? r end def pretty_print(pp) pp.group(name.inspect.size, name.inspect, '') { unless attrs.empty? pp.group(1, '[', ']') { pp.seplist(attrs, nil, :each_pair) {|k, v| pp.group { pp.pp k pp.text ' => ' pp.nest(k.inspect.size + 4) { pp.pp v } } } } end unless children.empty? && !attrs.empty? pp.group(1, '[', ']') { pp.breakable '' unless attrs.empty? pp.seplist(children) { |child| pp.pp child } } end } end def deepdup Marshal.load(Marshal.dump(self)) end def ctxget(ctx, name) if Hash === ctx if ctx.include?(name) return ctx[name] end elsif ctx.respond_to?("data_#{name}") return ctx.__send__("data_#{name}") end name end end end Randy.add_renderer(:data) { |tag, data| tag.children.clear tag[data] } Randy.add_renderer(:sequence) { |tag, data| header = tag.pattern(:header, :all) empty = tag.pattern(:empty) footer = tag.pattern(:footer, :all) item = tag.pattern(:item, :all) divider = tag.pattern(:divider, :all) divider = [[]] if divider.empty? tag.children.clear i = -1 content = data.map { |element| i += 1 [item[i % item.size].deepdup[:data => element], divider[i % divider.size]] } if content.empty? content = empty[:data => data] else content.last[-1] = '' # Remove last divider end tag[header, content, footer] } Randy.add_renderer(:mapping) { |tag, data| data.each { |k, v| tag.fill_slot k, v } tag } class Symbol def [](*args) if self == :xml Randy::Sane.new(args.join) elsif self == :text || self == :return args elsif self == :comment Randy::Comment.new(args.join) else Randy::Element.new(self.to_s)[*args] end end end