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