class Builder::XmlBase
XmlBase
is a base class for building XML builders. See Builder::XmlMarkup
and Builder::XmlEvents
for examples.
Attributes
Public Class Methods
Source
# File lib/builder/xmlbase.rb 27 def initialize(indent=0, initial=0, encoding='utf-8') 28 @indent = indent 29 @level = initial 30 @encoding = encoding.downcase 31 end
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.
Public Instance Methods
Source
# File lib/builder/xmlbase.rb 116 def <<(text) 117 _text(text) 118 end
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.
Source
# File lib/builder/xmlbase.rb 33 def explicit_nil_handling? 34 @explicit_nil_handling 35 end
Source
# File lib/builder/xmlbase.rb 90 def method_missing(sym, *args, &block) 91 cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls 92 tag!(sym, *args, &block) 93 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.
Source
# File lib/builder/xmlbase.rb 126 def nil? 127 false 128 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).
Source
# File lib/builder/xmlbase.rb 40 def tag!(sym, *args, &block) 41 text = nil 42 attrs = nil 43 sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol) 44 sym = sym.to_sym unless sym.class == ::Symbol 45 args.each do |arg| 46 case arg 47 when ::Hash 48 attrs ||= {} 49 attrs.merge!(arg) 50 when nil 51 attrs ||= {} 52 attrs.merge!({:nil => true}) if explicit_nil_handling? 53 else 54 text ||= ''.dup 55 text << arg.to_s 56 end 57 end 58 if block 59 unless text.nil? 60 ::Kernel::raise ::ArgumentError, 61 "XmlMarkup cannot mix a text argument with a block" 62 end 63 _indent 64 _start_tag(sym, attrs) 65 _newline 66 begin 67 _nested_structures(block) 68 ensure 69 _indent 70 _end_tag(sym) 71 _newline 72 end 73 elsif text.nil? 74 _indent 75 _start_tag(sym, attrs, true) 76 _newline 77 else 78 _indent 79 _start_tag(sym, attrs) 80 text! text 81 _end_tag(sym) 82 _newline 83 end 84 @target 85 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
.
Source
# File lib/builder/xmlbase.rb 99 def text!(text) 100 _text(_escape(text)) 101 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>
Private Instance Methods
Source
# File lib/builder/xmlbase.rb 134 def _escape(text) 135 result = XChar.encode(text) 136 begin 137 encoding = ::Encoding::find(@encoding) 138 raise Exception if encoding.dummy? 139 result.encode(encoding) 140 rescue 141 # if the encoding can't be supported, use numeric character references 142 result. 143 gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}. 144 force_encoding('ascii') 145 end 146 end
Source
# File lib/builder/xmlbase.rb 157 def _escape_attribute(text) 158 _escape(text).gsub("\n", " ").gsub("\r", " "). 159 gsub(%r{"}, '"') # " WART 160 end
Source
# File lib/builder/xmlbase.rb 167 def _indent 168 return if @indent == 0 || @level == 0 169 text!(" " * (@level * @indent)) 170 end
Source
# File lib/builder/xmlbase.rb 172 def _nested_structures(block) 173 @level += 1 174 block.call(self) 175 ensure 176 @level -= 1 177 end
Source
# File lib/builder/xmlbase.rb 162 def _newline 163 return if @indent == 0 164 text! "\n" 165 end
Source
# File lib/builder/xmlbase.rb 185 def cache_method_call(sym) 186 class << self; self; end.class_eval do 187 unless method_defined?(sym) 188 define_method(sym) do |*args, &block| 189 tag!(sym, *args, &block) 190 end 191 end 192 end 193 end
If XmlBase.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.