class Builder::XmlBase
XmlBase is a base class for building XML builders. See Builder::XmlMarkup and Builder::XmlEvents for examples.
Attributes
Public Class Methods
Create an XML markup builder.
- out
-
Object receiving the markup.
out
must respond to<<
. - indent
-
Number of spaces used for indentation (0 implies no indentation and no line breaks).
- initial
-
Level of initial indentation.
- encoding
-
When
encoding
and $KCODE are set to 'utf-8' characters aren't converted to character entities in the output stream.
# File lib/builder/xmlbase.rb, line 28 def initialize(indent=0, initial=0, encoding='utf-8') @indent = indent @level = initial @encoding = encoding.downcase end
Public Instance Methods
Append text to the output target without escaping any markup. May be used within the markup brackets as:
builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
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.
# File lib/builder/xmlbase.rb, line 117 def <<(text) _text(text) end
# File lib/builder/xmlbase.rb, line 34 def explicit_nil_handling? @explicit_nil_handling 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 that isn't cached.
# File lib/builder/xmlbase.rb, line 91 def method_missing(sym, *args, &block) cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls tag!(sym, *args, &block) 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. fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
# File lib/builder/xmlbase.rb, line 127 def nil? false end
Create a tag named sym
. Other than the first argument which
is the tag name, the arguments are the same as the tags implemented via
method_missing
.
# File lib/builder/xmlbase.rb, line 41 def tag!(sym, *args, &block) text = nil attrs = nil sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol) sym = sym.to_sym unless sym.class == ::Symbol args.each do |arg| case arg when ::Hash attrs ||= {} attrs.merge!(arg) when nil attrs ||= {} attrs.merge!({:nil => true}) if explicit_nil_handling? else text ||= '' text << arg.to_s end end if block unless text.nil? ::Kernel::raise ::ArgumentError, "XmlMarkup cannot mix a text argument with a block" end _indent _start_tag(sym, attrs) _newline begin _nested_structures(block) ensure _indent _end_tag(sym) _newline end 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 { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p>
# File lib/builder/xmlbase.rb, line 100 def text!(text) _text(_escape(text)) end
Private Instance Methods
# File lib/builder/xmlbase.rb, line 135 def _escape(text) result = XChar.encode(text) begin encoding = ::Encoding::find(@encoding) raise Exception if encoding.dummy? result.encode(encoding) rescue # if the encoding can't be supported, use numeric character references result. gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}. force_encoding('ascii') end end
# File lib/builder/xmlbase.rb, line 158 def _escape_attribute(text) _escape(text).gsub("\n", " ").gsub("\r", " "). gsub(%r{"}, '"') # " WART end
# File lib/builder/xmlbase.rb, line 168 def _indent return if @indent == 0 || @level == 0 text!(" " * (@level * @indent)) end
# File lib/builder/xmlbase.rb, line 173 def _nested_structures(block) @level += 1 block.call(self) ensure @level -= 1 end
# File lib/builder/xmlbase.rb, line 163 def _newline return if @indent == 0 text! "\n" end
If ::cache_method_calls = true, we dynamicly create the method missed as an instance method on the XMLBase object. Because XML documents are usually very repetative in nature, the next node will be handled by the new method instead of method_missing. As #method_missing is very slow, this speeds up document generation significantly.
# File lib/builder/xmlbase.rb, line 186 def cache_method_call(sym) class << self; self; end.class_eval do unless method_defined?(sym) define_method(sym) do |*args, &block| tag!(sym, *args, &block) end end end end