diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2007-10-10 23:01:41 +0000 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2007-10-10 23:01:41 +0000 |
commit | e7040b09e8dc381c746a0a3b4dff540eebe7a35d (patch) | |
tree | 842c2cfeb39de4cb23d20f910297d6b6da7be221 /activesupport | |
parent | cff25aae90859d5f47d827b162b505f8b316b733 (diff) | |
download | rails-e7040b09e8dc381c746a0a3b4dff540eebe7a35d.tar.gz rails-e7040b09e8dc381c746a0a3b4dff540eebe7a35d.tar.bz2 rails-e7040b09e8dc381c746a0a3b4dff540eebe7a35d.zip |
Rebundle Builder 2.1.2 but prefer a newer RubyGem if available.
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@7828 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activesupport')
14 files changed, 1061 insertions, 16 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index b2bf1735fc..92767bafa3 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Rebundle Builder 2.1.2 but prefer a newer RubyGem if available. [Jeremy Kemper] + * Add Range#overlaps?(range), Range#include?(range), and Range#step without a block. [brandon] * Correct BufferedLogger#level? checks. #9806 [wildchild, Johan Sorensen] diff --git a/activesupport/Rakefile b/activesupport/Rakefile index a056caa945..f8322c4c9c 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -48,8 +48,6 @@ spec = Gem::Specification.new do |s| s.require_path = 'lib' s.has_rdoc = true - s.add_dependency('builder', '~> 2.1.2') - s.author = "David Heinemeier Hansson" s.email = "david@loudthinking.com" s.homepage = "http://www.rubyonrails.org" diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 94f14ead01..e33f6ceb27 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -22,19 +22,9 @@ #++ $:.unshift(File.dirname(__FILE__)) -$:.unshift(File.dirname(__FILE__) + "/active_support/vendor") - -require 'rubygems' -begin - require 'builder' -rescue LoadError - $stderr.puts 'Builder has been unbundled from Active Support in Rails 2.0.' - $stderr.puts 'Please `gem install builder`' - raise -end +require 'active_support/vendor' require 'active_support/basic_object' - require 'active_support/inflector' require 'active_support/core_ext' diff --git a/activesupport/lib/active_support/basic_object.rb b/activesupport/lib/active_support/basic_object.rb index 1464f9d9b7..5e79de993f 100644 --- a/activesupport/lib/active_support/basic_object.rb +++ b/activesupport/lib/active_support/basic_object.rb @@ -1,5 +1,5 @@ # Ruby 1.9 introduces BasicObject. Use Builder's BlankSlate until then. unless defined? BasicObject - require 'builder/blankslate' - BasicObject = Builder::BlankSlate + require 'blankslate' + BasicObject = BlankSlate end diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 6ac913e5b9..74fe577030 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,8 +1,8 @@ require 'date' -require 'xml_simple' require 'cgi' require 'base64' require 'builder' +require 'xmlsimple' # Extensions needed for Hash#to_query class Object diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb new file mode 100644 index 0000000000..75c18062c0 --- /dev/null +++ b/activesupport/lib/active_support/vendor.rb @@ -0,0 +1,14 @@ +# Prefer gems to the bundled libs. +require 'rubygems' + +begin + gem 'builder', '~> 2.1.2' +rescue Gem::LoadError + $:.unshift "#{File.dirname(__FILE__)}/vendor/builder-2.1.2" +end + +begin + gem 'xml-simple', '~> 1.0.11' +rescue Gem::LoadError + $:.unshift "#{File.dirname(__FILE__)}/vendor/xml-simple-1.0.11" +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/blankslate.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/blankslate.rb new file mode 100644 index 0000000000..da6034d9c4 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/blankslate.rb @@ -0,0 +1,113 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +###################################################################### +# BlankSlate provides an abstract base class with no predefined +# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>). +# BlankSlate is useful as a base class when writing classes that +# depend upon <tt>method_missing</tt> (e.g. dynamic proxies). +# +class BlankSlate + class << self + + # Hide the method named +name+ in the BlankSlate class. Don't + # hide +instance_eval+ or any method beginning with "__". + def hide(name) + if instance_methods.include?(name.to_s) and + name !~ /^(__|instance_eval)/ + @hidden_methods ||= {} + @hidden_methods[name.to_sym] = instance_method(name) + undef_method name + end + end + + def find_hidden_method(name) + @hidden_methods ||= {} + @hidden_methods[name] || superclass.find_hidden_method(name) + end + + # Redefine a previously hidden method so that it may be called on a blank + # slate object. + def reveal(name) + bound_method = nil + unbound_method = find_hidden_method(name) + fail "Don't know how to reveal method '#{name}'" unless unbound_method + define_method(name) do |*args| + bound_method ||= unbound_method.bind(self) + bound_method.call(*args) + end + end + end + + instance_methods.each { |m| hide(m) } +end + +###################################################################### +# Since Ruby is very dynamic, methods added to the ancestors of +# BlankSlate <em>after BlankSlate is defined</em> will show up in the +# list of available BlankSlate methods. We handle this by defining a +# hook in the Object and Kernel classes that will hide any method +# defined after BlankSlate has been loaded. +# +module Kernel + class << self + alias_method :blank_slate_method_added, :method_added + + # Detect method additions to Kernel and remove them in the + # BlankSlate class. + def method_added(name) + result = blank_slate_method_added(name) + return result if self != Kernel + BlankSlate.hide(name) + result + end + end +end + +###################################################################### +# Same as above, except in Object. +# +class Object + class << self + alias_method :blank_slate_method_added, :method_added + + # Detect method additions to Object and remove them in the + # BlankSlate class. + def method_added(name) + result = blank_slate_method_added(name) + return result if self != Object + BlankSlate.hide(name) + result + end + + def find_hidden_method(name) + nil + end + end +end + +###################################################################### +# Also, modules included into Object need to be scanned and have their +# instance methods removed from blank slate. In theory, modules +# included into Kernel would have to be removed as well, but a +# "feature" of Ruby prevents late includes into modules from being +# exposed in the first place. +# +class Module + alias blankslate_original_append_features append_features + def append_features(mod) + result = blankslate_original_append_features(mod) + return result if mod != Object + instance_methods.each do |name| + BlankSlate.hide(name) + end + result + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder.rb new file mode 100644 index 0000000000..9719277669 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder.rb @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' +require 'builder/xmlevents' diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb new file mode 100644 index 0000000000..2935b6f1d1 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'blankslate' + +###################################################################### +# BlankSlate has been promoted to a top level name and is now +# available as a standalone gem. We make the name available in the +# Builder namespace for compatibility. +# +module Builder + BlankSlate = ::BlankSlate +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/css.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/css.rb new file mode 100644 index 0000000000..e086a1b132 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/css.rb @@ -0,0 +1,250 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org). +# Copyright 2005 by Scott Barron (scott@elitists.net). +# All rights reserved. +# +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +# +# Much of this is taken from Jim's work in xmlbase.rb and xmlmarkup.rb. +# Documentation has also been copied and pasted and modified to reflect +# that we're building CSS here instead of XML. Jim is conducting the +# orchestra here and I'm just off in the corner playing a flute. +#++ + +# Provide a flexible and easy to use Builder for creating Cascading +# Style Sheets (CSS). + + +require 'builder/blankslate' + +module Builder + + # Create a Cascading Style Sheet (CSS) using Ruby. + # + # Example usage: + # + # css = Builder::CSS.new + # + # text_color = '#7F7F7F' + # preferred_fonts = 'Helvetica, Arial, sans_serif' + # + # css.comment! 'This is our stylesheet' + # css.body { + # background_color '#FAFAFA' + # font_size 'small' + # font_family preferred_fonts + # color text_color + # } + # + # css.id!('navbar') { + # width '500px' + # } + # + # css.class!('navitem') { + # color 'red' + # } + # + # css.a :hover { + # text_decoration 'underline' + # } + # + # css.div(:id => 'menu') { + # background 'green' + # } + # + # css.div(:class => 'foo') { + # background 'red' + # } + # + # This will yield the following stylesheet: + # + # /* This is our stylesheet */ + # body { + # background_color: #FAFAFA; + # font_size: small; + # font_family: Helvetica, Arial, sans_serif; + # color: #7F7F7F; + # } + # + # #navbar { + # width: 500px; + # } + # + # .navitem { + # color: red; + # } + # + # a:hover { + # text_decoration: underline; + # } + # + # div#menu { + # background: green; + # } + # + # div.foo { + # background: red; + # } + # + class CSS < BlankSlate + + # Create a CSS builder. + # + # out:: Object receiving the markup.1 +out+ must respond to + # <tt><<</tt>. + # indent:: Number of spaces used for indentation (0 implies no + # indentation and no line breaks). + # + def initialize(indent=2) + @indent = indent + @target = [] + @parts = [] + @library = {} + end + + def +(part) + _join_with_op! '+' + self + end + + def >>(part) + _join_with_op! '' + self + end + + def >(part) + _join_with_op! '>' + self + end + + def |(part) + _join_with_op! ',' + self + end + + # Return the target of the builder + def target! + @target * '' + end + + # Create a comment string in the output. + def comment!(comment_text) + @target << "/* #{comment_text} */\n" + end + + def id!(arg, &block) + _start_container('#'+arg.to_s, nil, block_given?) + _css_block(block) if block + _unify_block + self + end + + def class!(arg, &block) + _start_container('.'+arg.to_s, nil, block_given?) + _css_block(block) if block + _unify_block + self + end + + def store!(sym, &block) + @library[sym] = block.to_proc + end + + def group!(*args, &block) + args.each do |arg| + if arg.is_a?(Symbol) + instance_eval(&@library[arg]) + else + instance_eval(&arg) + end + _text ', ' unless arg == args.last + end + if block + _css_block(block) + _unify_block + end + end + + def method_missing(sym, *args, &block) + sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol) + if block + _start_container(sym, args.first) + _css_block(block) + _unify_block + elsif @in_block + _indent + _css_line(sym, *args) + _newline + return self + else + _start_container(sym, args.first, false) + _unify_block + end + self + end + + # "Cargo culted" from Jim who also "cargo culted" it. See xmlbase.rb. + def nil? + false + end + + private + def _unify_block + @target << @parts * '' + @parts = [] + end + + def _join_with_op!(op) + rhs, lhs = @target.pop, @target.pop + @target << "#{lhs} #{op} #{rhs}" + end + + def _text(text) + @parts << text + end + + def _css_block(block) + _newline + _nested_structures(block) + _end_container + _end_block + end + + def _end_block + _newline + _newline + end + + def _newline + _text "\n" + end + + def _indent + _text ' ' * @indent + end + + def _nested_structures(block) + @in_block = true + self.instance_eval(&block) + @in_block = false + end + + def _start_container(sym, atts = {}, with_bracket = true) + selector = sym.to_s + selector << ".#{atts[:class]}" if atts && atts[:class] + selector << '#' + "#{atts[:id]}" if atts && atts[:id] + @parts << "#{selector}#{with_bracket ? ' {' : ''}" + end + + def _end_container + @parts << "}" + end + + def _css_line(sym, *args) + _text("#{sym.to_s.gsub('_','-')}: #{args * ' '};") + end + end +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb new file mode 100644 index 0000000000..8bdbd05899 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb @@ -0,0 +1,115 @@ +#!/usr/bin/env ruby + +# The XChar library is provided courtesy of Sam Ruby (See +# http://intertwingly.net/stories/2005/09/28/xchar.rb) + +# -------------------------------------------------------------------- + +# If the Builder::XChar module is not currently defined, fail on any +# name clashes in standard library classes. + +module Builder + def self.check_for_name_collision(klass, method_name, defined_constant=nil) + if klass.instance_methods.include?(method_name.to_s) + fail RuntimeError, + "Name Collision: Method '#{method_name}' is already defined in #{klass}" + end + end +end + +if ! defined?(Builder::XChar) + Builder.check_for_name_collision(String, "to_xs") + Builder.check_for_name_collision(Fixnum, "xchr") +end + +###################################################################### +module Builder + + #################################################################### + # XML Character converter, from Sam Ruby: + # (see http://intertwingly.net/stories/2005/09/28/xchar.rb). + # + module XChar # :nodoc: + + # See + # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows + # for details. + CP1252 = { # :nodoc: + 128 => 8364, # euro sign + 130 => 8218, # single low-9 quotation mark + 131 => 402, # latin small letter f with hook + 132 => 8222, # double low-9 quotation mark + 133 => 8230, # horizontal ellipsis + 134 => 8224, # dagger + 135 => 8225, # double dagger + 136 => 710, # modifier letter circumflex accent + 137 => 8240, # per mille sign + 138 => 352, # latin capital letter s with caron + 139 => 8249, # single left-pointing angle quotation mark + 140 => 338, # latin capital ligature oe + 142 => 381, # latin capital letter z with caron + 145 => 8216, # left single quotation mark + 146 => 8217, # right single quotation mark + 147 => 8220, # left double quotation mark + 148 => 8221, # right double quotation mark + 149 => 8226, # bullet + 150 => 8211, # en dash + 151 => 8212, # em dash + 152 => 732, # small tilde + 153 => 8482, # trade mark sign + 154 => 353, # latin small letter s with caron + 155 => 8250, # single right-pointing angle quotation mark + 156 => 339, # latin small ligature oe + 158 => 382, # latin small letter z with caron + 159 => 376, # latin capital letter y with diaeresis + } + + # See http://www.w3.org/TR/REC-xml/#dt-chardata for details. + PREDEFINED = { + 38 => '&', # ampersand + 60 => '<', # left angle bracket + 62 => '>', # right angle bracket + } + + # See http://www.w3.org/TR/REC-xml/#charsets for details. + VALID = [ + 0x9, 0xA, 0xD, + (0x20..0xD7FF), + (0xE000..0xFFFD), + (0x10000..0x10FFFF) + ] + end + +end + + +###################################################################### +# Enhance the Fixnum class with a XML escaped character conversion. +# +class Fixnum + XChar = Builder::XChar if ! defined?(XChar) + + # XML escaped version of chr + def xchr + n = XChar::CP1252[self] || self + case n when *XChar::VALID + XChar::PREDEFINED[n] or (n<128 ? n.chr : "&##{n};") + else + '*' + end + end +end + + +###################################################################### +# Enhance the String class with a XML escaped character version of +# to_s. +# +class String + # XML escaped version of to_s + def to_xs + unpack('U*').map {|n| n.xchr}.join # ASCII, UTF-8 + rescue + unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252 + end +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb new file mode 100644 index 0000000000..ace4b56d59 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb @@ -0,0 +1,139 @@ +#!/usr/bin/env ruby + +require '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. + # + def initialize(indent=0, initial=0) + @indent = indent + @level = initial + 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? + raise ArgumentError, "XmlMarkup cannot mix a text argument with a block" + end + _indent + _start_tag(sym, attrs) + _newline + _nested_structures(block) + _indent + _end_tag(sym) + _newline + 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' + def _escape(text) + text.to_xs + 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 + end +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb new file mode 100644 index 0000000000..91fcd21e13 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby + +#-- +# Copyright 2004 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +require 'builder/xmlmarkup' + +module Builder + + # Create a series of SAX-like XML events (e.g. start_tag, end_tag) + # from the markup code. XmlEvent objects are used in a way similar + # to XmlMarkup objects, except that a series of events are generated + # and passed to a handler rather than generating character-based + # markup. + # + # Usage: + # xe = Builder::XmlEvents.new(hander) + # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler. + # + # Indentation may also be selected by providing value for the + # indentation size and initial indentation level. + # + # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level) + # + # == XML Event Handler + # + # The handler object must expect the following events. + # + # [<tt>start_tag(tag, attrs)</tt>] + # Announces that a new tag has been found. +tag+ is the name of + # the tag and +attrs+ is a hash of attributes for the tag. + # + # [<tt>end_tag(tag)</tt>] + # Announces that an end tag for +tag+ has been found. + # + # [<tt>text(text)</tt>] + # Announces that a string of characters (+text+) has been found. + # A series of characters may be broken up into more than one + # +text+ call, so the client cannot assume that a single + # callback contains all the text data. + # + class XmlEvents < XmlMarkup + def text!(text) + @target.text(text) + end + + def _start_tag(sym, attrs, end_too=false) + @target.start_tag(sym, attrs) + _end_tag(sym) if end_too + end + + def _end_tag(sym) + @target.end_tag(sym) + end + end + +end diff --git a/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb new file mode 100644 index 0000000000..2dc6459ed4 --- /dev/null +++ b/activesupport/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb @@ -0,0 +1,328 @@ +#!/usr/bin/env ruby +#-- +# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org). +# All rights reserved. + +# Permission is granted for use, copying, modification, distribution, +# and distribution of modified versions of this work as long as the +# above copyright notice is included. +#++ + +# Provide a flexible and easy to use Builder for creating XML markup. +# See XmlBuilder for usage details. + +require 'builder/xmlbase' + +module Builder + + # Create XML markup easily. All (well, almost all) methods sent to + # an XmlMarkup object will be translated to the equivalent XML + # markup. Any method with a block will be treated as an XML markup + # tag with nested markup in the block. + # + # Examples will demonstrate this easier than words. In the + # following, +xm+ is an +XmlMarkup+ object. + # + # xm.em("emphasized") # => <em>emphasized</em> + # xm.em { xmm.b("emp & bold") } # => <em><b>emph & bold</b></em> + # xm.a("A Link", "href"=>"http://onestepback.org") + # # => <a href="http://onestepback.org">A Link</a> + # xm.div { br } # => <div><br/></div> + # xm.target("name"=>"compile", "option"=>"fast") + # # => <target option="fast" name="compile"\> + # # NOTE: order of attributes is not specified. + # + # xm.instruct! # <?xml version="1.0" encoding="UTF-8"?> + # xm.html { # <html> + # xm.head { # <head> + # xm.title("History") # <title>History</title> + # } # </head> + # xm.body { # <body> + # xm.comment! "HI" # <!-- HI --> + # xm.h1("Header") # <h1>Header</h1> + # xm.p("paragraph") # <p>paragraph</p> + # } # </body> + # } # </html> + # + # == Notes: + # + # * The order that attributes are inserted in markup tags is + # undefined. + # + # * Sometimes you wish to insert text without enclosing tags. Use + # the <tt>text!</tt> method to accomplish this. + # + # Example: + # + # xm.div { # <div> + # xm.text! "line"; xm.br # line<br/> + # xm.text! "another line"; xmbr # another line<br/> + # } # </div> + # + # * The special XML characters <, >, and & are converted to <, + # > and & automatically. Use the <tt><<</tt> operation to + # insert text without modification. + # + # * Sometimes tags use special characters not allowed in ruby + # identifiers. Use the <tt>tag!</tt> method to handle these + # cases. + # + # Example: + # + # xml.tag!("SOAP:Envelope") { ... } + # + # will produce ... + # + # <SOAP:Envelope> ... </SOAP:Envelope>" + # + # <tt>tag!</tt> will also take text and attribute arguments (after + # the tag name) like normal markup methods. (But see the next + # bullet item for a better way to handle XML namespaces). + # + # * Direct support for XML namespaces is now available. If the + # first argument to a tag call is a symbol, it will be joined to + # the tag to produce a namespace:tag combination. It is easier to + # show this than describe it. + # + # xml.SOAP :Envelope do ... end + # + # Just put a space before the colon in a namespace to produce the + # right form for builder (e.g. "<tt>SOAP:Envelope</tt>" => + # "<tt>xml.SOAP :Envelope</tt>") + # + # * XmlMarkup builds the markup in any object (called a _target_) + # that accepts the <tt><<</tt> method. If no target is given, + # then XmlMarkup defaults to a string target. + # + # Examples: + # + # xm = Builder::XmlMarkup.new + # result = xm.title("yada") + # # result is a string containing the markup. + # + # buffer = "" + # xm = Builder::XmlMarkup.new(buffer) + # # The markup is appended to buffer (using <<) + # + # xm = Builder::XmlMarkup.new(STDOUT) + # # The markup is written to STDOUT (using <<) + # + # xm = Builder::XmlMarkup.new + # x2 = Builder::XmlMarkup.new(:target=>xm) + # # Markup written to +x2+ will be send to +xm+. + # + # * Indentation is enabled by providing the number of spaces to + # indent for each level as a second argument to XmlBuilder.new. + # Initial indentation may be specified using a third parameter. + # + # Example: + # + # xm = Builder.new(:indent=>2) + # # xm will produce nicely formatted and indented XML. + # + # xm = Builder.new(:indent=>2, :margin=>4) + # # xm will produce nicely formatted and indented XML with 2 + # # spaces per indent and an over all indentation level of 4. + # + # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2) + # builder.name { |b| b.first("Jim"); b.last("Weirich) } + # # prints: + # # <name> + # # <first>Jim</first> + # # <last>Weirich</last> + # # </name> + # + # * The instance_eval implementation which forces self to refer to + # the message receiver as self is now obsolete. We now use normal + # block calls to execute the markup block. This means that all + # markup methods must now be explicitly send to the xml builder. + # For instance, instead of + # + # xml.div { strong("text") } + # + # you need to write: + # + # xml.div { xml.strong("text") } + # + # Although more verbose, the subtle change in semantics within the + # block was found to be prone to error. To make this change a + # little less cumbersome, the markup block now gets the markup + # object sent as an argument, allowing you to use a shorter alias + # within the block. + # + # For example: + # + # xml_builder = Builder::XmlMarkup.new + # xml_builder.div { |xml| + # xml.stong("text") + # } + # + class XmlMarkup < XmlBase + + # Create an XML markup builder. Parameters are specified by an + # option hash. + # + # :target=><em>target_object</em>:: + # Object receiving the markup. +out+ must respond to the + # <tt><<</tt> operator. The default is a plain string target. + # + # :indent=><em>indentation</em>:: + # Number of spaces used for indentation. The default is no + # indentation and no line breaks. + # + # :margin=><em>initial_indentation_level</em>:: + # Amount of initial indentation (specified in levels, not + # spaces). + # + # :escape_attrs=><b>OBSOLETE</em>:: + # The :escape_attrs option is no longer supported by builder + # (and will be quietly ignored). String attribute values are + # now automatically escaped. If you need unescaped attribute + # values (perhaps you are using entities in the attribute + # values), then give the value as a Symbol. This allows much + # finer control over escaping attribute values. + # + def initialize(options={}) + indent = options[:indent] || 0 + margin = options[:margin] || 0 + super(indent, margin) + @target = options[:target] || "" + end + + # Return the target of the builder. + def target! + @target + end + + def comment!(comment_text) + _ensure_no_block block_given? + _special("<!-- ", " -->", comment_text, nil) + end + + # Insert an XML declaration into the XML markup. + # + # For example: + # + # xml.declare! :ELEMENT, :blah, "yada" + # # => <!ELEMENT blah "yada"> + def declare!(inst, *args, &block) + _indent + @target << "<!#{inst}" + args.each do |arg| + case arg + when String + @target << %{ "#{arg}"} # " WART + when Symbol + @target << " #{arg}" + end + end + if block_given? + @target << " [" + _newline + _nested_structures(block) + @target << "]" + end + @target << ">" + _newline + end + + # Insert a processing instruction into the XML markup. E.g. + # + # For example: + # + # xml.instruct! + # #=> <?xml version="1.0" encoding="UTF-8"?> + # xml.instruct! :aaa, :bbb=>"ccc" + # #=> <?aaa bbb="ccc"?> + # + def instruct!(directive_tag=:xml, attrs={}) + _ensure_no_block block_given? + if directive_tag == :xml + a = { :version=>"1.0", :encoding=>"UTF-8" } + attrs = a.merge attrs + end + _special( + "<?#{directive_tag}", + "?>", + nil, + attrs, + [:version, :encoding, :standalone]) + end + + # Insert a CDATA section into the XML markup. + # + # For example: + # + # xml.cdata!("text to be included in cdata") + # #=> <![CDATA[text to be included in cdata]]> + # + def cdata!(text) + _ensure_no_block block_given? + _special("<![CDATA[", "]]>", text, nil) + end + + private + + # NOTE: All private methods of a builder object are prefixed when + # a "_" character to avoid possible conflict with XML tag names. + + # Insert text directly in to the builder's target. + def _text(text) + @target << text + end + + # Insert special instruction. + def _special(open, close, data=nil, attrs=nil, order=[]) + _indent + @target << open + @target << data if data + _insert_attributes(attrs, order) if attrs + @target << close + _newline + end + + # Start an XML tag. If <tt>end_too</tt> is true, then the start + # tag is also the end tag (e.g. <br/> + def _start_tag(sym, attrs, end_too=false) + @target << "<#{sym}" + _insert_attributes(attrs) + @target << "/" if end_too + @target << ">" + end + + # Insert an ending tag. + def _end_tag(sym) + @target << "</#{sym}>" + end + + # Insert the attributes (given in the hash). + def _insert_attributes(attrs, order=[]) + return if attrs.nil? + order.each do |k| + v = attrs[k] + @target << %{ #{k}="#{_attr_value(v)}"} if v # " WART + end + attrs.each do |k, v| + @target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART + end + end + + def _attr_value(value) + case value + when Symbol + value.to_s + else + _escape_quote(value.to_s) + end + end + + def _ensure_no_block(got_block) + if got_block + fail IllegalBlockError, + "Blocks are not allowed on XML instructions" + end + end + + end + +end |