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

<<(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
118 def <<(text)
119   _text(text)
120 end
explicit_nil_handling?() click to toggle source
   # File lib/builder/xmlbase.rb
35 def explicit_nil_handling?
36   @explicit_nil_handling
37 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
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
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
128 def nil?
129   false
130 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
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
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
101 def text!(text)
102   _text(_escape(text))
103 end

Private Instance Methods

_escape(text) click to toggle source
    # 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
_escape_attribute(text) click to toggle source
    # File lib/builder/xmlbase.rb
159 def _escape_attribute(text)
160   _escape(text).gsub("\n", "&#10;").gsub("\r", "&#13;").
161     gsub(%r{"}, '&quot;') # " WART
162 end
_indent() click to toggle source
    # File lib/builder/xmlbase.rb
169 def _indent
170   return if @indent == 0 || @level == 0
171   text!(" " * (@level * @indent))
172 end
_nested_structures(block) click to toggle source
    # File lib/builder/xmlbase.rb
174 def _nested_structures(block)
175   @level += 1
176   block.call(self)
177 ensure
178   @level -= 1
179 end
_newline() click to toggle source
    # File lib/builder/xmlbase.rb
164 def _newline
165   return if @indent == 0
166   text! "\n"
167 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
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