xmlmarkup.rb 11 KB

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