# = rexmlbuilder.rb # # == Copyright (c) 2006 Thomas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # == Author(s) # # * Thomas Sawyer # Author:: Thomas Sawyer # Copyright:: Copyright (c) 2006 Thomas Sawyer # License:: Ruby License require 'rexml/document' require 'facets/more/functor' # = REXMLBuilder # # Build XML Documents programatically with Ruby and REXML # via the Builder Pattern --made possible by Ruby's blocks. # # XmlBuilder uses REXML to contruct XML documents, helping to ensure # XML specification conforamcy. # # == Usage # # x = REXMLBuilder.new # # favs = [ 'cake', 'jelly', 'salt' ] # # x.table( :width=>'100%' ) { # x.tr { # favs.each { |v| x.td v } # } # } # } # # You can also setup the XmlBuilder to assume an implicit self, # so the explict reciever is not needed. # # x = REXMLBuilder.new( :implicit ) # # x.table( :width=>'100%' ) { # tr { # td "1" ; td "2" ; td "3" # } # } # } # # Implict building is more elegant in form, but it is not as # functional because it makes it more difficult to refer to # external values, ie. if 'favs' were used as in the previous # example, it would be interpreted as another tag entry, not # the array. class REXMLBuilder # Privatize a few Kernel methods that are most likely to clash, # but arn't essential here. TODO Maybe more in this context? private :class, :clone, :display, :type, :method, :to_a, :to_s # Output XML document. def to_s( indent=nil ) o = "" @target.write( o, indent || @options[:indent] || 0 ) o end # raw insert def <<( str ) r = builder.raw( str ) if Array === r r.each { |e| @target << e } else @target << r end self end # Redirection functor. #-- # TODO Better name? #++ def out( str=nil ) if str self << str else @builder_functor ||= Functor.new( &__method__(:+) ) end end # text output def to_s @target.to_s end private def builder REXMLUtil end # Prepare builder. def initialize( *options ) # initialize options @options = (Hash === options.last ? options.pop : {} ) options.each { |o| @options[o] = true } # initialize stack @stack = [] # initialize buffer @target = builder.new( @options ) end # Reroute -- Core functionality. def method_missing( tag, *args, &block ) tag = tag.to_s if tag[-1,1] == '?' raise ArgumentError if block_given? out.instruct( tag.chomp('?'), *args ) elsif tag[-1,1] == '!' out.declare( tag.chomp('!'), *args ) else out.element( tag, *args ) end if @current and block_given? @stack << @target @target = @current if @options[:implicit] instance_eval( &block ) else block.call end @target = @stack.pop end @target end # Creates object via builder module, # stores as current and appends to buffer. def +( op, *args, &blk ) obj = builder.send( op, *args, &blk ) @current = obj @target << obj self end end # Module provides the build methods in # an independent namespace. module REXMLBuilder::REXMLUtil extend self # Add raw XML markup def raw( str ) REXML::Document.new( "#{str}" ).root.to_a end # New REXML document. def document( options={} ) #@options = options doc = REXML::Document.new # xml declaration if options[:version] doc << xml( options[:version], options[:encoding], options[:standalone] ) end doc end # Used for initial taget in builder. alias_method :new, :document # XML declaration instruction. def xml( version=nil, *args ) REXML::XMLDecl.new( version=nil, *args ) end def element( tag, *args ) keys = (Hash === args.last ? args.pop : {}) skeys = {} keys.each { |k,v| skeys[k.to_s] = v.to_s } e = REXML::Element.new( tag ) e.add_attributes( skeys ) e.add_text(args.join(' ')) unless args.empty? e end def instruct( name, *args ) keys = (Hash === args.last ? args.pop : {}) content = ( args + keys.collect{ |k,v| %{#{k}="#{v}"} } ).join(' ') REXML::Instruction.new( name, content.strip ) end alias_method :instruction, :instruct alias_method :pi, :instruct def text( str, *args ) REXML::Text.new( str, *args ) end def comment( str, *args ) REXML::Comment.new( str, *args ) end def cdata( str, *args ) REXML::CData.new( str, *args ) end # DTD declarations def doctype!( *args ) REXML::DocType.new( *args ) end def element!( *args ) REXML::ElementDecl.new( *args ) end def attlist!( *args ) REXML::AttlistDecl.new( *args ) end def entity!( *args ) REXML::EntityDecl.new( *args ) end def notation!( *args ) REXML::NotationDecl.new( *args ) end end # TODO =begin x = REXMLBuilder.new( :version => '1.0' ) x.html { x.head { x.title "Test Document" } x.body( "try me" ){ x.out.text "No more!" x.r? "x" x.img :src=>"smile.jp" x.h1 "Try Me" x.h2 "This is a test." x.div { x.span "Try" } x.out "one" } } puts x =end