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