File: active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb

Overview
Module Structure
Class Hierarchy
Code

Overview

Module Structure

  module: <Toplevel Module>
  module: Builder#16
  class: XmlMarkup#160
inherits from
  XmlBase ( Builder )
has properties
method: initialize / 1 #185
method: target! #193
method: comment! / 1 #197
method: declare! / 3 #208
method: instruct! / 2 #238
method: cdata! / 1 #259
method: _text / 1 #270
method: _special / 5 #275
method: _start_tag / 3 #286
method: _end_tag / 1 #294
method: _insert_attributes / 2 #299
method: _attr_value / 1 #310
method: _ensure_no_block / 1 #319

Class Hierarchy

Object ( Builtin-Module )
BlankSlate
XmlBase ( Builder )
  XmlMarkup    #160

Code

   1  #!/usr/bin/env ruby
   2  #--
   3  # Copyright 2004, 2005 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  # Provide a flexible and easy to use Builder for creating XML markup.
  12  # See XmlBuilder for usage details.
  13 
  14  require 'builder/xmlbase'
  15 
  16  module Builder
  17 
  18    # Create XML markup easily.  All (well, almost all) methods sent to
  19    # an XmlMarkup object will be translated to the equivalent XML
  20    # markup.  Any method with a block will be treated as an XML markup
  21    # tag with nested markup in the block.
  22    #
  23    # Examples will demonstrate this easier than words.  In the
  24    # following, +xm+ is an +XmlMarkup+ object.
  25    #
  26    #   xm.em("emphasized")             # => <em>emphasized</em>
  27    #   xm.em { xmm.b("emp & bold") }   # => <em><b>emph &amp; bold</b></em>
  28    #   xm.a("A Link", "href"=>"http://onestepback.org")
  29    #                                   # => <a href="http://onestepback.org">A Link</a>
  30    #   xm.div { br }                    # => <div><br/></div>
  31    #   xm.target("name"=>"compile", "option"=>"fast")
  32    #                                   # => <target option="fast" name="compile"\>
  33    #                                   # NOTE: order of attributes is not specified.
  34    #
  35    #   xm.instruct!                   # <?xml version="1.0" encoding="UTF-8"?>
  36    #   xm.html {                      # <html>
  37    #     xm.head {                    #   <head>
  38    #       xm.title("History")        #     <title>History</title>
  39    #     }                            #   </head>
  40    #     xm.body {                    #   <body>
  41    #       xm.comment! "HI"           #     <! -- HI -->
  42    #       xm.h1("Header")            #     <h1>Header</h1>
  43    #       xm.p("paragraph")          #     <p>paragraph</p>
  44    #     }                            #   </body>
  45    #   }                              # </html>
  46    #
  47    # == Notes:
  48    #
  49    # * The order that attributes are inserted in markup tags is
  50    #   undefined.
  51    #
  52    # * Sometimes you wish to insert text without enclosing tags.  Use
  53    #   the <tt>text!</tt> method to accomplish this.
  54    #
  55    #   Example:
  56    #
  57    #     xm.div {                          # <div>
  58    #       xm.text! "line"; xm.br          #   line<br/>
  59    #       xm.text! "another line"; xmbr   #    another line<br/>
  60    #     }                                 # </div>
  61    #
  62    # * The special XML characters <, >, and & are converted to &lt;,
  63    #   &gt; and &amp; automatically.  Use the <tt><<</tt> operation to
  64    #   insert text without modification.
  65    #
  66    # * Sometimes tags use special characters not allowed in ruby
  67    #   identifiers.  Use the <tt>tag!</tt> method to handle these
  68    #   cases.
  69    #
  70    #   Example:
  71    #
  72    #     xml.tag!("SOAP:Envelope") { ... }
  73    #
  74    #   will produce ...
  75    #
  76    #     <SOAP:Envelope> ... </SOAP:Envelope>"
  77    #
  78    #   <tt>tag!</tt> will also take text and attribute arguments (after
  79    #   the tag name) like normal markup methods.  (But see the next
  80    #   bullet item for a better way to handle XML namespaces).
  81    #
  82    # * Direct support for XML namespaces is now available.  If the
  83    #   first argument to a tag call is a symbol, it will be joined to
  84    #   the tag to produce a namespace:tag combination.  It is easier to
  85    #   show this than describe it.
  86    #
  87    #     xml.SOAP :Envelope do ... end
  88    #
  89    #   Just put a space before the colon in a namespace to produce the
  90    #   right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
  91    #   "<tt>xml.SOAP :Envelope</tt>")
  92    #
  93    # * XmlMarkup builds the markup in any object (called a _target_)
  94    #   that accepts the <tt><<</tt> method.  If no target is given,
  95    #   then XmlMarkup defaults to a string target.
  96    #
  97    #   Examples:
  98    #
  99    #     xm = Builder::XmlMarkup.new
 100    #     result = xm.title("yada")
 101    #     # result is a string containing the markup.
 102    #
 103    #     buffer = ""
 104    #     xm = Builder::XmlMarkup.new(buffer)
 105    #     # The markup is appended to buffer (using <<)
 106    #
 107    #     xm = Builder::XmlMarkup.new(STDOUT)
 108    #     # The markup is written to STDOUT (using <<)
 109    #
 110    #     xm = Builder::XmlMarkup.new
 111    #     x2 = Builder::XmlMarkup.new(:target=>xm)
 112    #     # Markup written to +x2+ will be send to +xm+.
 113    #
 114    # * Indentation is enabled by providing the number of spaces to
 115    #   indent for each level as a second argument to XmlBuilder.new.
 116    #   Initial indentation may be specified using a third parameter.
 117    #
 118    #   Example:
 119    #
 120    #     xm = Builder.new(:indent=>2)
 121    #     # xm will produce nicely formatted and indented XML.
 122    #
 123    #     xm = Builder.new(:indent=>2, :margin=>4)
 124    #     # xm will produce nicely formatted and indented XML with 2
 125    #     # spaces per indent and an over all indentation level of 4.
 126    #
 127    #     builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
 128    #     builder.name { |b| b.first("Jim"); b.last("Weirich) }
 129    #     # prints:
 130    #     #     <name>
 131    #     #       <first>Jim</first>
 132    #     #       <last>Weirich</last>
 133    #     #     </name>
 134    #
 135    # * The instance_eval implementation which forces self to refer to
 136    #   the message receiver as self is now obsolete.  We now use normal
 137    #   block calls to execute the markup block.  This means that all
 138    #   markup methods must now be explicitly send to the xml builder.
 139    #   For instance, instead of
 140    #
 141    #      xml.div { strong("text") }
 142    #
 143    #   you need to write:
 144    #
 145    #      xml.div { xml.strong("text") }
 146    #
 147    #   Although more verbose, the subtle change in semantics within the
 148    #   block was found to be prone to error.  To make this change a
 149    #   little less cumbersome, the markup block now gets the markup
 150    #   object sent as an argument, allowing you to use a shorter alias
 151    #   within the block.
 152    #
 153    #   For example:
 154    #
 155    #     xml_builder = Builder::XmlMarkup.new
 156    #     xml_builder.div { |xml|
 157    #       xml.stong("text")
 158    #     }
 159    #
 160    class XmlMarkup < XmlBase
 161 
 162      # Create an XML markup builder.  Parameters are specified by an
 163      # option hash.
 164      #
 165      # :target=><em>target_object</em>::
 166      #    Object receiving the markup.  +out+ must respond to the
 167      #    <tt><<</tt> operator.  The default is a plain string target.
 168      #
 169      # :indent=><em>indentation</em>::
 170      #    Number of spaces used for indentation.  The default is no
 171      #    indentation and no line breaks.
 172      #
 173      # :margin=><em>initial_indentation_level</em>::
 174      #    Amount of initial indentation (specified in levels, not
 175      #    spaces).
 176      #
 177      # :escape_attrs=><b>OBSOLETE</em>::
 178      #    The :escape_attrs option is no longer supported by builder
 179      #    (and will be quietly ignored).  String attribute values are
 180      #    now automatically escaped.  If you need unescaped attribute
 181      #    values (perhaps you are using entities in the attribute
 182      #    values), then give the value as a Symbol.  This allows much
 183      #    finer control over escaping attribute values.
 184      #
 185      def initialize(options={})
 186        indent = options[:indent] || 0
 187        margin = options[:margin] || 0
 188        super(indent, margin)
 189        @target = options[:target] || ""
 190      end
 191 
 192      # Return the target of the builder.
 193      def target!
 194        @target
 195      end
 196 
 197      def comment!(comment_text)
 198        _ensure_no_block block_given?
 199        _special("<!-- ", " -->", comment_text, nil)
 200      end
 201 
 202      # Insert an XML declaration into the XML markup.
 203      #
 204      # For example:
 205      #
 206      #   xml.declare! :ELEMENT, :blah, "yada"
 207      #       # => <!ELEMENT blah "yada">
 208      def declare!(inst, *args, &block)
 209        _indent
 210        @target << "<!#{inst}"
 211        args.each do |arg|
 212          case arg
 213          when String
 214            @target << %{ "#{arg}"} # " WART
 215          when Symbol
 216            @target << " #{arg}"
 217          end
 218        end
 219        if block_given?
 220          @target << " ["
 221          _newline
 222          _nested_structures(block)
 223          @target << "]"
 224        end
 225        @target << ">"
 226        _newline
 227      end
 228 
 229      # Insert a processing instruction into the XML markup.  E.g.
 230      #
 231      # For example:
 232      #
 233      #    xml.instruct!
 234      #        #=> <?xml version="1.0" encoding="UTF-8"?>
 235      #    xml.instruct! :aaa, :bbb=>"ccc"
 236      #        #=> <?aaa bbb="ccc"?>
 237      #
 238      def instruct!(directive_tag=:xml, attrs={})
 239        _ensure_no_block block_given?
 240        if directive_tag == :xml
 241          a = { :version=>"1.0", :encoding=>"UTF-8" }
 242          attrs = a.merge attrs
 243        end
 244        _special(
 245        "<?#{directive_tag}",
 246        "?>",
 247        nil,
 248        attrs,
 249        [:version, :encoding, :standalone])
 250      end
 251 
 252      # Insert a CDATA section into the XML markup.
 253      #
 254      # For example:
 255      #
 256      #    xml.cdata!("text to be included in cdata")
 257      #        #=> <![CDATA[text to be included in cdata]]>
 258      #
 259      def cdata!(text)
 260        _ensure_no_block block_given?
 261        _special("<![CDATA[", "]]>", text, nil)
 262      end
 263 
 264      private
 265 
 266      # NOTE: All private methods of a builder object are prefixed when
 267      # a "_" character to avoid possible conflict with XML tag names.
 268 
 269      # Insert text directly in to the builder's target.
 270      def _text(text)
 271        @target << text
 272      end
 273 
 274      # Insert special instruction.
 275      def _special(open, close, data=nil, attrs=nil, order=[])
 276        _indent
 277        @target << open
 278        @target << data if data
 279        _insert_attributes(attrs, order) if attrs
 280        @target << close
 281        _newline
 282      end
 283 
 284      # Start an XML tag.  If <tt>end_too</tt> is true, then the start
 285      # tag is also the end tag (e.g.  <br/>
 286      def _start_tag(sym, attrs, end_too=false)
 287        @target << "<#{sym}"
 288        _insert_attributes(attrs)
 289        @target << "/" if end_too
 290        @target << ">"
 291      end
 292 
 293      # Insert an ending tag.
 294      def _end_tag(sym)
 295        @target << "</#{sym}>"
 296      end
 297 
 298      # Insert the attributes (given in the hash).
 299      def _insert_attributes(attrs, order=[])
 300        return if attrs.nil?
 301        order.each do |k|
 302          v = attrs[k]
 303          @target << %{ #{k}="#{_attr_value(v)}"} if v # " WART
 304        end
 305        attrs.each do |k, v|
 306          @target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART
 307        end
 308      end
 309 
 310      def _attr_value(value)
 311        case value
 312        when Symbol
 313          value.to_s
 314        else
 315          _escape_quote(value.to_s)
 316        end
 317      end
 318 
 319      def _ensure_no_block(got_block)
 320        if got_block
 321          fail IllegalBlockError,
 322          "Blocks are not allowed on XML instructions"
 323        end
 324      end
 325 
 326    end
 327 
 328  end