#!/usr/bin/env ruby require 'builder/blankslate' module Builder #:nodoc: # Generic error for builder class IllegalBlockError < RuntimeError #:nodoc: end # XmlBase is a base class for building XML builders. See # Builder::XmlMarkup and Builder::XmlEvents for examples. class XmlBase < BlankSlate #:nodoc: # Create an XML markup builder. # # out:: Object receiving the markup.1 +out+ must respond to # <<. # indent:: Number of spaces used for indentation (0 implies no # indentation and no line breaks). # initial:: Level of initial indentation. # def initialize(indent=0, initial=0) @indent = indent @level = initial end # Create a tag named +sym+. Other than the first argument which # is the tag name, the arguements are the same as the tags # implemented via method_missing. def tag!(sym, *args, &block) self.__send__(sym, *args, &block) end # Create XML markup based on the name of the method. This method # is never invoked directly, but is called for each markup method # in the markup block. def method_missing(sym, *args, &block) text = nil attrs = nil sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol) args.each do |arg| case arg when Hash attrs ||= {} attrs.merge!(arg) else text ||= '' text << arg.to_s end end if block unless text.nil? raise ArgumentError, "XmlMarkup cannot mix a text argument with a block" end _capture_outer_self(block) if @self.nil? _indent _start_tag(sym, attrs) _newline _nested_structures(block) _indent _end_tag(sym) _newline elsif text.nil? _indent _start_tag(sym, attrs, true) _newline else _indent _start_tag(sym, attrs) text! text _end_tag(sym) _newline end @target end # Append text to the output target. Escape any markup. May be # used within the markup brackets as: # # builder.p { br; text! "HI" } #=>


HI

def text!(text) _text(_escape(text)) end # Append text to the output target without escaping any markup. # May be used within the markup brackets as: # # builder.p { |x| x << "
HI" } #=>


HI

# # This is useful when using non-builder enabled software that # generates strings. Just insert the string directly into the # builder without changing the inserted markup. # # It is also useful for stacking builder objects. Builders only # use << to append to the target, so by supporting this # method/operation builders can use other builders as their # targets. def <<(text) _text(text) end # For some reason, nil? is sent to the XmlMarkup object. If nil? # is not defined and method_missing is invoked, some strange kind # of recursion happens. Since nil? won't ever be an XML tag, it # is pretty safe to define it here. (Note: this is an example of # cargo cult programming, # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming). def nil? false end private def _escape(text) text. gsub(%r{&}, '&'). gsub(%r{<}, '<'). gsub(%r{>}, '>') end def _capture_outer_self(block) @self = eval("self", block) end def _newline return if @indent == 0 text! "\n" end def _indent return if @indent == 0 || @level == 0 text!(" " * (@level * @indent)) end def _nested_structures(block) @level += 1 block.call(self) ensure @level -= 1 end end end