class Builder::XmlBase

XmlBase is a base class for building XML builders. See Builder::XmlMarkup and Builder::XmlEvents for examples.

Attributes

cache_method_calls[RW]

Public Class Methods

new(indent=0, initial=0, encoding='utf-8') click to toggle source

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
27 def initialize(indent=0, initial=0, encoding='utf-8')
28   @indent = indent
29   @level  = initial
30   @encoding = encoding.downcase
31 end

Public Instance Methods

<<(text) click to toggle source

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
116 def <<(text)
117   _text(text)
118 end
explicit_nil_handling?() click to toggle source
   # File lib/builder/xmlbase.rb
33 def explicit_nil_handling?
34   @explicit_nil_handling
35 end
method_missing(sym, *args, &block) click to toggle source

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
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
nil?() click to toggle source

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
126 def nil?
127   false
128 end
tag!(sym, *args, &block) click to toggle source

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
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
text!(text) click to toggle source

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
 99 def text!(text)
100   _text(_escape(text))
101 end

Private Instance Methods

_escape(text) click to toggle 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
_escape_attribute(text) click to toggle source
    # File lib/builder/xmlbase.rb
157 def _escape_attribute(text)
158   _escape(text).gsub("\n", "&#10;").gsub("\r", "&#13;").
159     gsub(%r{"}, '&quot;') # " WART
160 end
_indent() click to toggle source
    # File lib/builder/xmlbase.rb
167 def _indent
168   return if @indent == 0 || @level == 0
169   text!(" " * (@level * @indent))
170 end
_nested_structures(block) click to toggle 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
_newline() click to toggle source
    # File lib/builder/xmlbase.rb
162 def _newline
163   return if @indent == 0
164   text! "\n"
165 end
cache_method_call(sym) click to toggle source

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
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