lib/builder/xmlbase.rb in builder-3.0.0 vs lib/builder/xmlbase.rb in builder-3.0.1
- old
+ new
@@ -9,10 +9,14 @@
# XmlBase is a base class for building XML builders. See
# Builder::XmlMarkup and Builder::XmlEvents for examples.
class XmlBase < BlankSlate
+ class << self
+ attr_accessor :cache_method_calls
+ end
+
# Create an XML markup builder.
#
# out:: Object receiving the markup. +out+ must respond to
# <tt><<</tt>.
# indent:: Number of spaces used for indentation (0 implies no
@@ -24,25 +28,19 @@
def initialize(indent=0, initial=0, encoding='utf-8')
@indent = indent
@level = initial
@encoding = encoding.downcase
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 <tt>method_missing</tt>.
def tag!(sym, *args, &block)
- method_missing(sym.to_sym, *args, &block)
- 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.
- def method_missing(sym, *args, &block)
text = nil
attrs = nil
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
+ sym = sym.to_sym unless sym.class == ::Symbol
args.each do |arg|
case arg
when ::Hash
attrs ||= {}
attrs.merge!(arg)
@@ -78,18 +76,26 @@
_newline
end
@target
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.
+ def method_missing(sym, *args, &block)
+ cache_method_call(sym) if ::Builder::XmlBase.cache_method_calls
+ tag!(sym, *args, &block)
+ 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>
def text!(text)
_text(_escape(text))
end
-
+
# 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>
#
@@ -102,11 +108,11 @@
# method/operation builders can use other builders as their
# targets.
def <<(text)
_text(text)
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,
@@ -114,11 +120,11 @@
def nil?
false
end
private
-
+
require 'builder/xchar'
if ::String.method_defined?(:encode)
def _escape(text)
result = XChar.encode(text)
begin
@@ -130,31 +136,53 @@
force_encoding('ascii')
end
end
else
def _escape(text)
- text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))
+ if (text.method(:to_xs).arity == 0)
+ text.to_xs
+ else
+ text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))
+ end
end
end
- def _escape_quote(text)
- _escape(text).gsub(%r{"}, '"') # " WART
+ def _escape_attribute(text)
+ _escape(text).gsub("\n", " ").gsub("\r", " ").
+ gsub(%r{"}, '"') # " WART
end
def _newline
return if @indent == 0
text! "\n"
end
-
+
def _indent
return if @indent == 0 || @level == 0
text!(" " * (@level * @indent))
end
-
+
def _nested_structures(block)
@level += 1
block.call(self)
ensure
@level -= 1
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.
+ def cache_method_call(sym)
+ instance_eval <<-NEW_METHOD
+ def #{sym.to_s}(*args, &block)
+ tag!(:#{sym.to_s}, *args, &block)
+ end
+ NEW_METHOD
+ end
end
+
+ XmlBase.cache_method_calls = true
+
end