clarion-builder 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ = Builder 1.2.4 Released.
2
+
3
+ Added a "CDATA" method to the XML Markup builder (from Josh Knowles).
4
+
5
+ == What is Builder?
6
+
7
+ Builder::XmlMarkup allows easy programmatic creation of XML markup.
8
+ For example:
9
+
10
+ builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
11
+ builder.person { |b| b.name("Jim"); b.phone("555-1234") }
12
+ puts builder.target!
13
+
14
+ will generate:
15
+
16
+ <person>
17
+ <name>Jim</name>
18
+ <phone>555-1234</phone>
19
+ </person>
20
+
21
+ == Availability
22
+
23
+ The easiest way to get and install builder is via RubyGems ...
24
+
25
+ gem install builder (you may need root/admin privileges)
26
+
27
+ == Thanks
28
+
29
+ * Josh Knowles for the cdata! patch.
30
+
31
+ -- Jim Weirich
@@ -0,0 +1,46 @@
1
+ = Builder 2.0.0 Released.
2
+
3
+ == Changes in 2.0.0
4
+
5
+ * UTF-8 characters in data are now correctly translated to their XML
6
+ equivalents. (Thanks to Sam Ruby)
7
+
8
+ * Attribute values are now escaped by default. See the README
9
+ file for details.
10
+
11
+ <b>NOTE:</b> The escaping attribute values by default is different
12
+ than in previous releases of Builder. This makes version 2.0.0
13
+ somewhat incompatible with the 1.x series of Builder. If you use "&",
14
+ "<", or ">" in attributes values, you may have to change your
15
+ code. (Essentially you remove the manual escaping. The new way is
16
+ easier, believe me).
17
+
18
+ == What is Builder?
19
+
20
+ Builder::XmlMarkup is a library that allows easy programmatic creation
21
+ of XML markup. For example:
22
+
23
+ builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
24
+ builder.person { |b| b.name("Jim"); b.phone("555-1234") }
25
+
26
+ will generate:
27
+
28
+ <person>
29
+ <name>Jim</name>
30
+ <phone>555-1234</phone>
31
+ </person>
32
+
33
+ == Availability
34
+
35
+ The easiest way to get and install builder is via RubyGems ...
36
+
37
+ gem install builder (you may need root/admin privileges)
38
+
39
+ == Thanks
40
+
41
+ * Sam Ruby for the XChar module and the related UTF-8 translation
42
+ tools.
43
+ * Also to Sam Ruby for gently persuading me to start quoting attribute
44
+ values.
45
+
46
+ -- Jim Weirich
@@ -0,0 +1,58 @@
1
+ = Builder 2.1.1 Released.
2
+
3
+ Release 2.1.1 of Builder is mainly a bug fix release.
4
+
5
+ == Changes in 2.1.1
6
+
7
+ * Added <tt>reveal</tt> capability to BlankSlate.
8
+
9
+ * Fixed a bug in BlankSlate where including a module into Object could
10
+ cause methods to leak into BlankSlate.
11
+
12
+ * Fixed typo in XmlMarkup class docs (from Martin Fowler).
13
+
14
+ * Fixed test on private methods to differentiate between targetted and
15
+ untargetted private methods.
16
+
17
+ * Removed legacy capture of @self in XmlBase (@self was used back when
18
+ we used instance eval).
19
+
20
+ * Added additional tests for global functions (both direct and
21
+ included).
22
+
23
+ * Several misc internal cleanups, including rearranging the source
24
+ code tree.
25
+
26
+ <b>NOTE:</b> The escaping attribute values by default is different
27
+ than in previous releases of Builder. This makes version 2.0.x
28
+ somewhat incompatible with the 1.x series of Builder. If you use "&",
29
+ "<", or ">" in attributes values, you may have to change your
30
+ code. (Essentially you remove the manual escaping. The new way is
31
+ easier, believe me).
32
+
33
+ == What is Builder?
34
+
35
+ Builder::XmlMarkup is a library that allows easy programmatic creation
36
+ of XML markup. For example:
37
+
38
+ builder = Builder::XmlMarkup.new(:target=>STDOUT, :indent=>2)
39
+ builder.person { |b| b.name("Jim"); b.phone("555-1234") }
40
+
41
+ will generate:
42
+
43
+ <person>
44
+ <name>Jim</name>
45
+ <phone>555-1234</phone>
46
+ </person>
47
+
48
+ == Availability
49
+
50
+ The easiest way to get and install builder is via RubyGems ...
51
+
52
+ gem install builder (you may need root/admin privileges)
53
+
54
+ == Thanks
55
+
56
+ * Martin Fowler for spotting some typos in the documentation.
57
+
58
+ -- Jim Weirich
data/lib/blankslate.rb ADDED
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
4
+ # All rights reserved.
5
+
6
+ # Permission is granted for use, copying, modification, distribution,
7
+ # and distribution of modified versions of this work as long as the
8
+ # above copyright notice is included.
9
+ #++
10
+
11
+ class String
12
+ if instance_methods.first.is_a?(Symbol)
13
+ def _blankslate_as_name
14
+ to_sym
15
+ end
16
+ else
17
+ def _blankslate_as_name
18
+ self
19
+ end
20
+ end
21
+ end
22
+
23
+ class Symbol
24
+ if instance_methods.first.is_a?(Symbol)
25
+ def _blankslate_as_name
26
+ self
27
+ end
28
+ else
29
+ def _blankslate_as_name
30
+ to_s
31
+ end
32
+ end
33
+ end
34
+
35
+ ######################################################################
36
+ # BlankSlate provides an abstract base class with no predefined
37
+ # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
38
+ # BlankSlate is useful as a base class when writing classes that
39
+ # depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
40
+ #
41
+ class BlankSlate
42
+ class << self
43
+
44
+ # Hide the method named +name+ in the BlankSlate class. Don't
45
+ # hide +instance_eval+ or any method beginning with "__".
46
+ def hide(name)
47
+ warn_level = $VERBOSE
48
+ $VERBOSE = nil
49
+ if instance_methods.include?(name._blankslate_as_name) &&
50
+ name !~ /^(__|instance_eval$)/
51
+ @hidden_methods ||= {}
52
+ @hidden_methods[name.to_sym] = instance_method(name)
53
+ undef_method name
54
+ end
55
+ ensure
56
+ $VERBOSE = warn_level
57
+ end
58
+
59
+ def find_hidden_method(name)
60
+ @hidden_methods ||= {}
61
+ @hidden_methods[name] || superclass.find_hidden_method(name)
62
+ end
63
+
64
+ # Redefine a previously hidden method so that it may be called on a blank
65
+ # slate object.
66
+ def reveal(name)
67
+ hidden_method = find_hidden_method(name)
68
+ fail "Don't know how to reveal method '#{name}'" unless hidden_method
69
+ define_method(name, hidden_method)
70
+ end
71
+ end
72
+
73
+ instance_methods.each { |m| hide(m) }
74
+ end
75
+
76
+ ######################################################################
77
+ # Since Ruby is very dynamic, methods added to the ancestors of
78
+ # BlankSlate <em>after BlankSlate is defined</em> will show up in the
79
+ # list of available BlankSlate methods. We handle this by defining a
80
+ # hook in the Object and Kernel classes that will hide any method
81
+ # defined after BlankSlate has been loaded.
82
+ #
83
+ module Kernel
84
+ class << self
85
+ alias_method :blank_slate_method_added, :method_added
86
+
87
+ # Detect method additions to Kernel and remove them in the
88
+ # BlankSlate class.
89
+ def method_added(name)
90
+ result = blank_slate_method_added(name)
91
+ return result if self != Kernel
92
+ BlankSlate.hide(name)
93
+ result
94
+ end
95
+ end
96
+ end
97
+
98
+ ######################################################################
99
+ # Same as above, except in Object.
100
+ #
101
+ class Object
102
+ class << self
103
+ alias_method :blank_slate_method_added, :method_added
104
+
105
+ # Detect method additions to Object and remove them in the
106
+ # BlankSlate class.
107
+ def method_added(name)
108
+ result = blank_slate_method_added(name)
109
+ return result if self != Object
110
+ BlankSlate.hide(name)
111
+ result
112
+ end
113
+
114
+ def find_hidden_method(name)
115
+ nil
116
+ end
117
+ end
118
+ end
119
+
120
+ ######################################################################
121
+ # Also, modules included into Object need to be scanned and have their
122
+ # instance methods removed from blank slate. In theory, modules
123
+ # included into Kernel would have to be removed as well, but a
124
+ # "feature" of Ruby prevents late includes into modules from being
125
+ # exposed in the first place.
126
+ #
127
+ # NOTE (keeper): that Ruby "feature" is gone on modern Rubies -- a
128
+ # module included into Kernel late IS visible through BlankSlate's
129
+ # ancestry now, so Kernel includes are scanned too. (Fixes
130
+ # test_late_included_module_in_kernel_is_ok on Ruby >= 1.9.)
131
+ #
132
+ class Module
133
+ alias blankslate_original_append_features append_features
134
+ def append_features(mod)
135
+ result = blankslate_original_append_features(mod)
136
+ return result if mod != Object && mod != Kernel
137
+ instance_methods.each do |name|
138
+ BlankSlate.hide(name)
139
+ end
140
+ result
141
+ end
142
+ end
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ #--
3
+ # Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
4
+ # All rights reserved.
5
+
6
+ # Permission is granted for use, copying, modification, distribution,
7
+ # and distribution of modified versions of this work as long as the
8
+ # above copyright notice is included.
9
+ #++
10
+
11
+ ######################################################################
12
+ # BlankSlate has been promoted to a top level name and is now
13
+ # available as a standalone gem. We make the name available in the
14
+ # Builder namespace for compatibility.
15
+ #
16
+ module Builder
17
+ if Object::const_defined?(:BasicObject)
18
+ BlankSlate = ::BasicObject
19
+ else
20
+ require 'blankslate'
21
+ BlankSlate = ::BlankSlate
22
+ end
23
+ end
@@ -0,0 +1,8 @@
1
+ module Builder
2
+ VERSION_NUMBERS = [
3
+ VERSION_MAJOR = 1,
4
+ VERSION_MINOR = 0,
5
+ VERSION_BUILD = 0,
6
+ ]
7
+ VERSION = VERSION_NUMBERS.join(".")
8
+ end
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # The XChar library is provided courtesy of Sam Ruby (See
4
+ # http://intertwingly.net/stories/2005/09/28/xchar.rb)
5
+
6
+ # --------------------------------------------------------------------
7
+
8
+ # If the Builder::XChar module is not currently defined, fail on any
9
+ # name clashes in standard library classes.
10
+
11
+ module Builder
12
+ def self.check_for_name_collision(klass, method_name, defined_constant=nil)
13
+ if klass.method_defined?(method_name.to_s)
14
+ fail RuntimeError,
15
+ "Name Collision: Method '#{method_name}' is already defined in #{klass}"
16
+ end
17
+ end
18
+ end
19
+
20
+ if ! defined?(Builder::XChar) and ! String.method_defined?(:encode)
21
+ Builder.check_for_name_collision(String, "to_xs")
22
+ Builder.check_for_name_collision(Fixnum, "xchr")
23
+ end
24
+
25
+ ######################################################################
26
+ module Builder
27
+
28
+ ####################################################################
29
+ # XML Character converter, from Sam Ruby:
30
+ # (see http://intertwingly.net/stories/2005/09/28/xchar.rb).
31
+ #
32
+ module XChar # :nodoc:
33
+
34
+ # See
35
+ # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows
36
+ # for details.
37
+ CP1252 = { # :nodoc:
38
+ 128 => 8364, # euro sign
39
+ 130 => 8218, # single low-9 quotation mark
40
+ 131 => 402, # latin small letter f with hook
41
+ 132 => 8222, # double low-9 quotation mark
42
+ 133 => 8230, # horizontal ellipsis
43
+ 134 => 8224, # dagger
44
+ 135 => 8225, # double dagger
45
+ 136 => 710, # modifier letter circumflex accent
46
+ 137 => 8240, # per mille sign
47
+ 138 => 352, # latin capital letter s with caron
48
+ 139 => 8249, # single left-pointing angle quotation mark
49
+ 140 => 338, # latin capital ligature oe
50
+ 142 => 381, # latin capital letter z with caron
51
+ 145 => 8216, # left single quotation mark
52
+ 146 => 8217, # right single quotation mark
53
+ 147 => 8220, # left double quotation mark
54
+ 148 => 8221, # right double quotation mark
55
+ 149 => 8226, # bullet
56
+ 150 => 8211, # en dash
57
+ 151 => 8212, # em dash
58
+ 152 => 732, # small tilde
59
+ 153 => 8482, # trade mark sign
60
+ 154 => 353, # latin small letter s with caron
61
+ 155 => 8250, # single right-pointing angle quotation mark
62
+ 156 => 339, # latin small ligature oe
63
+ 158 => 382, # latin small letter z with caron
64
+ 159 => 376, # latin capital letter y with diaeresis
65
+ }
66
+
67
+ # See http://www.w3.org/TR/REC-xml/#dt-chardata for details.
68
+ PREDEFINED = {
69
+ 38 => '&amp;', # ampersand
70
+ 60 => '&lt;', # left angle bracket
71
+ 62 => '&gt;', # right angle bracket
72
+ }
73
+
74
+ # See http://www.w3.org/TR/REC-xml/#charsets for details.
75
+ VALID = [
76
+ 0x9, 0xA, 0xD,
77
+ (0x20..0xD7FF),
78
+ (0xE000..0xFFFD),
79
+ (0x10000..0x10FFFF)
80
+ ]
81
+
82
+ # http://www.fileformat.info/info/unicode/char/fffd/index.htm
83
+ REPLACEMENT_CHAR =
84
+ if String.method_defined?(:encode)
85
+ "\uFFFD"
86
+ elsif $KCODE == 'UTF8'
87
+ "\xEF\xBF\xBD"
88
+ else
89
+ '*'
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+
96
+ if String.method_defined?(:encode)
97
+ module Builder
98
+ module XChar # :nodoc:
99
+ CP1252_DIFFERENCES, UNICODE_EQUIVALENT = Builder::XChar::CP1252.each.
100
+ inject([[],[]]) {|(domain,range),(key,value)|
101
+ [domain << key,range << value]
102
+ }.map {|seq| seq.pack('U*').force_encoding('utf-8')}
103
+
104
+ XML_PREDEFINED = Regexp.new('[' +
105
+ Builder::XChar::PREDEFINED.keys.pack('U*').force_encoding('utf-8') +
106
+ ']')
107
+
108
+ INVALID_XML_CHAR = Regexp.new('[^'+
109
+ Builder::XChar::VALID.map { |item|
110
+ case item
111
+ when Integer
112
+ [item].pack('U').force_encoding('utf-8')
113
+ when Range
114
+ [item.first, '-'.ord, item.last].pack('UUU').force_encoding('utf-8')
115
+ end
116
+ }.join +
117
+ ']')
118
+
119
+ ENCODING_BINARY = Encoding.find('BINARY')
120
+ ENCODING_UTF8 = Encoding.find('UTF-8')
121
+ ENCODING_ISO1 = Encoding.find('ISO-8859-1')
122
+
123
+ # convert a string to valid UTF-8, compensating for a number of
124
+ # common errors.
125
+ def XChar.unicode(string)
126
+ if string.encoding == ENCODING_BINARY
127
+ if string.ascii_only?
128
+ string
129
+ else
130
+ string = string.clone.force_encoding(ENCODING_UTF8)
131
+ if string.valid_encoding?
132
+ string
133
+ else
134
+ string.encode(ENCODING_UTF8, ENCODING_ISO1)
135
+ end
136
+ end
137
+
138
+ elsif string.encoding == ENCODING_UTF8
139
+ if string.valid_encoding?
140
+ string
141
+ else
142
+ string.encode(ENCODING_UTF8, ENCODING_ISO1)
143
+ end
144
+
145
+ else
146
+ string.encode(ENCODING_UTF8)
147
+ end
148
+ end
149
+
150
+ # encode a string per XML rules
151
+ def XChar.encode(string)
152
+ unicode(string).
153
+ tr(CP1252_DIFFERENCES, UNICODE_EQUIVALENT).
154
+ gsub(INVALID_XML_CHAR, REPLACEMENT_CHAR).
155
+ gsub(XML_PREDEFINED) {|c| PREDEFINED[c.ord]}
156
+ end
157
+ end
158
+ end
159
+
160
+ else
161
+
162
+ ######################################################################
163
+ # Enhance the Fixnum class with a XML escaped character conversion.
164
+ #
165
+ class Fixnum
166
+ XChar = Builder::XChar if ! defined?(XChar)
167
+
168
+ # XML escaped version of chr. When <tt>escape</tt> is set to false
169
+ # the CP1252 fix is still applied but utf-8 characters are not
170
+ # converted to character entities.
171
+ def xchr(escape=true)
172
+ n = XChar::CP1252[self] || self
173
+ case n when *XChar::VALID
174
+ XChar::PREDEFINED[n] or
175
+ (n<128 ? n.chr : (escape ? "&##{n};" : [n].pack('U*')))
176
+ else
177
+ Builder::XChar::REPLACEMENT_CHAR
178
+ end
179
+ end
180
+ end
181
+
182
+
183
+ ######################################################################
184
+ # Enhance the String class with a XML escaped character version of
185
+ # to_s.
186
+ #
187
+ class String
188
+ # XML escaped version of to_s. When <tt>escape</tt> is set to false
189
+ # the CP1252 fix is still applied but utf-8 characters are not
190
+ # converted to character entities.
191
+ def to_xs(escape=true)
192
+ unpack('U*').map {|n| n.xchr(escape)}.join # ASCII, UTF-8
193
+ rescue
194
+ unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'builder/blankslate'
4
+
5
+ module Builder
6
+
7
+ # Generic error for builder
8
+ class IllegalBlockError < RuntimeError; end
9
+
10
+ # XmlBase is a base class for building XML builders. See
11
+ # Builder::XmlMarkup and Builder::XmlEvents for examples.
12
+ class XmlBase < BlankSlate
13
+
14
+ class << self
15
+ attr_accessor :cache_method_calls
16
+ end
17
+
18
+ # Create an XML markup builder.
19
+ #
20
+ # out :: Object receiving the markup. +out+ must respond to
21
+ # <tt><<</tt>.
22
+ # indent :: Number of spaces used for indentation (0 implies no
23
+ # indentation and no line breaks).
24
+ # initial :: Level of initial indentation.
25
+ # encoding :: When <tt>encoding</tt> and $KCODE are set to 'utf-8'
26
+ # characters aren't converted to character entities in
27
+ # the output stream.
28
+ def initialize(indent=0, initial=0, encoding='utf-8')
29
+ @indent = indent
30
+ @level = initial
31
+ @encoding = encoding.downcase
32
+ end
33
+
34
+ def explicit_nil_handling?
35
+ @explicit_nil_handling
36
+ end
37
+
38
+ # Create a tag named +sym+. Other than the first argument which
39
+ # is the tag name, the arguments are the same as the tags
40
+ # implemented via <tt>method_missing</tt>.
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
87
+
88
+ # Create XML markup based on the name of the method. This method
89
+ # is never invoked directly, but is called for each markup method
90
+ # in the markup block that isn't cached.
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
95
+
96
+ # Append text to the output target. Escape any markup. May be
97
+ # used within the markup brackets as:
98
+ #
99
+ # builder.p { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p>
100
+ def text!(text)
101
+ _text(_escape(text))
102
+ end
103
+
104
+ # Append text to the output target without escaping any markup.
105
+ # May be used within the markup brackets as:
106
+ #
107
+ # builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
108
+ #
109
+ # This is useful when using non-builder enabled software that
110
+ # generates strings. Just insert the string directly into the
111
+ # builder without changing the inserted markup.
112
+ #
113
+ # It is also useful for stacking builder objects. Builders only
114
+ # use <tt><<</tt> to append to the target, so by supporting this
115
+ # method/operation builders can use other builders as their
116
+ # targets.
117
+ def <<(text)
118
+ _text(text)
119
+ end
120
+
121
+ # For some reason, nil? is sent to the XmlMarkup object. If nil?
122
+ # is not defined and method_missing is invoked, some strange kind
123
+ # of recursion happens. Since nil? won't ever be an XML tag, it
124
+ # is pretty safe to define it here. (Note: this is an example of
125
+ # cargo cult programming,
126
+ # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
127
+ def nil?
128
+ false
129
+ end
130
+
131
+ private
132
+
133
+ require 'builder/xchar'
134
+ if ::String.method_defined?(:encode)
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
148
+ else
149
+ def _escape(text)
150
+ if (text.method(:to_xs).arity == 0)
151
+ text.to_xs
152
+ else
153
+ text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))
154
+ end
155
+ end
156
+ end
157
+
158
+ def _escape_attribute(text)
159
+ _escape(text).gsub("\n", "&#10;").gsub("\r", "&#13;").
160
+ gsub(%r{"}, '&quot;') # " WART
161
+ end
162
+
163
+ def _newline
164
+ return if @indent == 0
165
+ text! "\n"
166
+ end
167
+
168
+ def _indent
169
+ return if @indent == 0 || @level == 0
170
+ text!(" " * (@level * @indent))
171
+ end
172
+
173
+ def _nested_structures(block)
174
+ @level += 1
175
+ block.call(self)
176
+ ensure
177
+ @level -= 1
178
+ end
179
+
180
+ # If XmlBase.cache_method_calls = true, we dynamicly create the method
181
+ # missed as an instance method on the XMLBase object. Because XML
182
+ # documents are usually very repetative in nature, the next node will
183
+ # be handled by the new method instead of method_missing. As
184
+ # method_missing is very slow, this speeds up document generation
185
+ # significantly.
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
195
+ end
196
+
197
+ XmlBase.cache_method_calls = true
198
+
199
+ end