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