| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 | #!/usr/bin/env rubyrequire 'builder/blankslate'module Builder  # Generic error for builder  class IllegalBlockError < RuntimeError; end  # XmlBase is a base class for building XML builders.  See  # Builder::XmlMarkup and Builder::XmlEvents for examples.  class XmlBase < BlankSlate    # 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    #            indentation and no line breaks).    # initial::  Level of initial indentation.    # encoding:: When <tt>encoding</tt> and $KCODE are set to 'utf-8'    #            characters aren't converted to character entities in    #            the output stream.    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)      args.each do |arg|        case arg        when ::Hash          attrs ||= {}          attrs.merge!(arg)        else          text ||= ''          text << arg.to_s        end      end      if block        unless text.nil?          ::Kernel::raise ::ArgumentError,            "XmlMarkup cannot mix a text argument with a block"        end        _indent        _start_tag(sym, attrs)        _newline        begin          _nested_structures(block)        ensure          _indent          _end_tag(sym)          _newline        end      elsif text.nil?        _indent        _start_tag(sym, attrs, true)        _newline      else        _indent        _start_tag(sym, attrs)        text! text        _end_tag(sym)        _newline      end      @target    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>    #    # 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 <tt><<</tt> to append to the target, so by supporting this    # 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,    # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).    def nil?      false    end    private        require 'builder/xchar'    if ::String.method_defined?(:encode)      def _escape(text)        result = XChar.encode(text)        begin          result.encode(@encoding)        rescue          # if the encoding can't be supported, use numeric character references          result.            gsub(/[^\u0000-\u007F]/) {|c| "&##{c.ord};"}.            force_encoding('ascii')        end      end    else      def _escape(text)        text.to_xs((@encoding != 'utf-8' or $KCODE != 'UTF8'))      end    end    def _escape_quote(text)      _escape(text).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  endend
 |