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{"}, '&quot;') # " WART + def _escape_attribute(text) + _escape(text).gsub("\n", "&#10;").gsub("\r", "&#13;"). + gsub(%r{"}, '&quot;') # " 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