erector 0.8.3 → 0.9.0.pre1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. data/Gemfile +21 -0
  2. data/Rakefile +171 -0
  3. data/VERSION.yml +3 -2
  4. data/lib/erector.rb +3 -5
  5. data/lib/erector/abstract_widget.rb +76 -31
  6. data/lib/erector/attributes.rb +34 -0
  7. data/lib/erector/caching.rb +1 -0
  8. data/lib/erector/convenience.rb +12 -4
  9. data/lib/erector/dependency.rb +2 -1
  10. data/lib/erector/element.rb +113 -0
  11. data/lib/erector/erect/erect.rb +10 -7
  12. data/lib/erector/externals.rb +2 -1
  13. data/lib/erector/html.rb +6 -340
  14. data/lib/erector/html_widget.rb +300 -0
  15. data/lib/erector/inline.rb +1 -1
  16. data/lib/erector/output.rb +39 -12
  17. data/lib/erector/promise.rb +137 -0
  18. data/lib/erector/rails2/extensions/rails_widget.rb +1 -1
  19. data/lib/erector/rails3.rb +1 -1
  20. data/lib/erector/sass.rb +14 -19
  21. data/lib/erector/tag.rb +65 -0
  22. data/lib/erector/text.rb +123 -0
  23. data/lib/erector/version.rb +1 -1
  24. data/lib/erector/widget.rb +52 -12
  25. data/lib/erector/widgets/page.rb +12 -10
  26. data/lib/erector/xml_widget.rb +131 -0
  27. data/spec/erector/caching_spec.rb +1 -0
  28. data/spec/erector/externals_spec.rb +0 -1
  29. data/spec/erector/html_spec.rb +9 -25
  30. data/spec/erector/output_spec.rb +102 -9
  31. data/spec/erector/promise_spec.rb +173 -0
  32. data/spec/erector/sass_spec.rb +1 -1
  33. data/spec/erector/tag_spec.rb +67 -0
  34. data/spec/erector/widget_spec.rb +53 -2
  35. data/spec/erector/xml_widget_spec.rb +74 -0
  36. data/spec/rails2/rails_app/Gemfile +1 -0
  37. data/spec/rails2/rails_app/spec/rails_spec_helper.rb +2 -0
  38. data/spec/rails_root/Gemfile +11 -0
  39. data/spec/rails_root/README +256 -0
  40. data/spec/rails_root/Rakefile +7 -0
  41. data/spec/rails_root/app/controllers/application.rb +6 -0
  42. data/spec/rails_root/app/controllers/application_controller.rb +3 -0
  43. data/spec/rails_root/app/helpers/application_helper.rb +2 -0
  44. data/spec/rails_root/app/views/layouts/application.html.erb +14 -0
  45. data/spec/rails_root/app/views/test/_erb.erb +1 -0
  46. data/spec/rails_root/app/views/test/_erector.rb +5 -0
  47. data/spec/rails_root/app/views/test/_partial_with_locals.rb +7 -0
  48. data/spec/rails_root/app/views/test/bare.rb +5 -0
  49. data/spec/rails_root/app/views/test/erb_from_erector.html.rb +5 -0
  50. data/spec/rails_root/app/views/test/erector_from_erb.html.erb +1 -0
  51. data/spec/rails_root/app/views/test/erector_with_locals_from_erb.html.erb +6 -0
  52. data/spec/rails_root/app/views/test/implicit_assigns.html.rb +5 -0
  53. data/spec/rails_root/app/views/test/needs.html.rb +7 -0
  54. data/spec/rails_root/app/views/test/needs_subclass.html.rb +5 -0
  55. data/spec/rails_root/app/views/test/protected_instance_variable.html.rb +5 -0
  56. data/spec/rails_root/app/views/test/render_default.html.rb +5 -0
  57. data/spec/rails_root/app/views/test/render_partial.html.rb +5 -0
  58. data/spec/rails_root/config.ru +4 -0
  59. data/spec/rails_root/config/application.rb +42 -0
  60. data/spec/rails_root/config/boot.rb +13 -0
  61. data/spec/rails_root/config/database.yml +22 -0
  62. data/spec/rails_root/config/environment.rb +5 -0
  63. data/spec/rails_root/config/environments/development.rb +22 -0
  64. data/spec/rails_root/config/environments/production.rb +49 -0
  65. data/spec/rails_root/config/environments/test.rb +35 -0
  66. data/spec/rails_root/config/initializers/backtrace_silencers.rb +7 -0
  67. data/spec/rails_root/config/initializers/inflections.rb +10 -0
  68. data/spec/rails_root/config/initializers/mime_types.rb +5 -0
  69. data/spec/rails_root/config/initializers/secret_token.rb +7 -0
  70. data/spec/rails_root/config/initializers/session_store.rb +8 -0
  71. data/spec/rails_root/config/locales/en.yml +5 -0
  72. data/spec/rails_root/config/routes.rb +58 -0
  73. data/spec/rails_root/db/seeds.rb +7 -0
  74. data/spec/rails_root/doc/README_FOR_APP +2 -0
  75. data/spec/rails_root/log/development.log +17 -0
  76. data/spec/rails_root/log/test.log +3750 -0
  77. data/spec/rails_root/public/404.html +26 -0
  78. data/spec/rails_root/public/422.html +26 -0
  79. data/spec/rails_root/public/500.html +26 -0
  80. data/spec/rails_root/public/dispatch.cgi +10 -0
  81. data/spec/rails_root/public/dispatch.fcgi +24 -0
  82. data/spec/rails_root/public/dispatch.rb +10 -0
  83. data/spec/rails_root/public/favicon.ico +0 -0
  84. data/spec/rails_root/public/images/rails.png +0 -0
  85. data/spec/rails_root/public/index.html +262 -0
  86. data/spec/rails_root/public/javascripts/application.js +2 -0
  87. data/spec/rails_root/public/javascripts/controls.js +965 -0
  88. data/spec/rails_root/public/javascripts/dragdrop.js +974 -0
  89. data/spec/rails_root/public/javascripts/effects.js +1123 -0
  90. data/spec/rails_root/public/javascripts/prototype.js +6001 -0
  91. data/spec/rails_root/public/javascripts/rails.js +175 -0
  92. data/spec/rails_root/public/robots.txt +5 -0
  93. data/spec/rails_root/script/about +3 -0
  94. data/spec/rails_root/script/console +3 -0
  95. data/spec/rails_root/script/destroy +3 -0
  96. data/spec/rails_root/script/generate +3 -0
  97. data/spec/rails_root/script/performance/benchmarker +3 -0
  98. data/spec/rails_root/script/performance/profiler +3 -0
  99. data/spec/rails_root/script/performance/request +3 -0
  100. data/spec/rails_root/script/plugin +3 -0
  101. data/spec/rails_root/script/process/inspector +3 -0
  102. data/spec/rails_root/script/process/reaper +3 -0
  103. data/spec/rails_root/script/process/spawner +3 -0
  104. data/spec/rails_root/script/rails +6 -0
  105. data/spec/rails_root/script/runner +3 -0
  106. data/spec/rails_root/script/server +3 -0
  107. data/spec/rails_root/spec/form_builder_spec.rb +21 -0
  108. data/spec/rails_root/spec/rails_helpers_spec.rb +220 -0
  109. data/spec/rails_root/spec/rails_spec_helper.rb +10 -0
  110. data/spec/rails_root/spec/rails_widget_spec.rb +83 -0
  111. data/spec/rails_root/spec/render_spec.rb +298 -0
  112. data/spec/rails_root/test/performance/browsing_test.rb +9 -0
  113. data/spec/rails_root/test/test_helper.rb +13 -0
  114. data/spec/spec_helper.rb +3 -1
  115. metadata +202 -66
@@ -45,7 +45,7 @@ module Erector
45
45
 
46
46
  output_buffer = view.with_output_buffer do
47
47
  # Set parent to the view and use Rails's output buffer.
48
- new_output = Output.new { view.output_buffer }
48
+ new_output = Output.new :buffer => lambda { view.output_buffer }
49
49
  widget.to_html(options.merge(:parent => view,
50
50
  :output => new_output))
51
51
  end
@@ -39,7 +39,7 @@ module Erector
39
39
  # Set parent and helpers to the view and use Rails's output buffer.
40
40
  widget.to_html(options.merge(:helpers => view,
41
41
  :parent => view,
42
- :output => Output.new { view.output_buffer }))
42
+ :output => Output.new(:buffer => lambda { view.output_buffer })))
43
43
  end
44
44
  end
45
45
 
@@ -1,22 +1,17 @@
1
-
2
- if Object.const_defined?(:Sass)
3
- module Erector
4
- # Adds sass support to Erector widgets.
5
- # Note that sass is provided inside the gem named "haml",
6
- # not the gem named "sass".
7
- # To get sass support into your Erector project, install the
8
- # haml gem -- see http://sass-lang.com/download.html -- and
9
- # then do something like this:
10
- # require 'rubygems'
11
- # require 'sass'
12
- #
13
- # Current support is barebones. Please offer suggestions (or better
14
- # yet, patches) for whether and how to support, e.g., caching,
15
- # loading from files, precompilation, etc.
16
- module Sass
17
- def sass(sass_text)
18
- style ::Sass::Engine.new(sass_text, :cache => false).render
19
- end
1
+ module Erector
2
+ # Adds sass support to Erector widgets.
3
+ #
4
+ # Sass is an *optional dependency* of the Erector gem, so
5
+ # a call to +sass+ inside a widget will fail unless you have already
6
+ # installed the sass gem (e.g. "gem 'sass'" in your code or Gemfile).
7
+ #
8
+ # Current support is barebones. Please offer suggestions (or better
9
+ # yet, patches) for whether and how to support, e.g., caching,
10
+ # loading from files, precompilation, etc.
11
+ module Sass
12
+ def sass(sass_text)
13
+ require "sass"
14
+ style ::Sass::Engine.new(sass_text, :cache => false).render
20
15
  end
21
16
  end
22
17
  end
@@ -0,0 +1,65 @@
1
+ module Erector
2
+
3
+ # Defines a type of tag (not an actual element with attributes and contents)
4
+ class Tag
5
+
6
+ # Pass the self_closing and inline params as symbols, e.g.
7
+ #
8
+ # Tag.new("i", :inline)
9
+ # Tag.new("input", :inline, :self_closing)
10
+ #
11
+ # @param name the name of the tag, e.g. "div"
12
+ # @param self_closing whether it can (false) or cannot (true) contain text or other elements. Default: false
13
+ # @param inline whether it should appear in line with other elements (true) or on a line by itself (false) in pretty mode. Default: false
14
+ # @param snake whether to covert the method name into "snake case" (aka underscorized). Default: false
15
+ #
16
+ def initialize(name, *params)
17
+ @name = name.to_s
18
+ @method_name = if params.first.is_a? String
19
+ params.shift
20
+ else
21
+ @name
22
+ end
23
+ @self_closing = params.include?(:self_closing)
24
+ @inline = params.include?(:inline)
25
+ @method_name = snake_case(@method_name) if params.include?(:snake_case)
26
+ end
27
+
28
+ attr_reader :name, :method_name
29
+
30
+ def self_closing?
31
+ @self_closing
32
+ end
33
+
34
+ def newliney?
35
+ !@inline
36
+ end
37
+
38
+ def inline?
39
+ @inline
40
+ end
41
+
42
+ ##
43
+ # Convert to snake case.
44
+ #
45
+ # "FooBar".snake_case #=> "foo_bar"
46
+ # "HeadlineCNNNews".snake_case #=> "headline_cnn_news"
47
+ # "CNN".snake_case #=> "cnn"
48
+ #
49
+ # @return [String] Receiver converted to snake case.
50
+ #
51
+ # @api public
52
+ # borrowed from https://github.com/datamapper/extlib/blob/master/lib/extlib/string.rb
53
+ def snake_case(s)
54
+ if s.match(/\A[A-Z]+\z/)
55
+ s.downcase
56
+ else
57
+ s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
58
+ gsub(/([a-z])([A-Z])/, '\1_\2').
59
+ downcase
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,123 @@
1
+ require "erector/raw_string"
2
+
3
+ module Erector
4
+ module Text
5
+ # Emits text to the output buffer, e.g.
6
+ #
7
+ # text "my dog smells awful"
8
+ # => "my dog smells awful"
9
+ #
10
+ # If a string is passed in, it will be HTML-escaped. If the
11
+ # result of calling methods such as raw is passed in, the HTML will not be
12
+ # HTML-escaped again. If another kind of object is passed in, the result
13
+ # of calling its to_s method will be treated as a string would be.
14
+ #
15
+ # You shouldn't pass a widget in to this method, as that will cause
16
+ # performance problems (as well as being semantically goofy). Use the
17
+ # #widget method instead.
18
+ #
19
+ # You may pass a series of values (i.e. varargs). In that case, each value
20
+ # will be emitted to the output stream in turn. You can specify a delimiter
21
+ # by using an options hash with as the final argument, using +:join+ as the key,
22
+ # e.g.
23
+ #
24
+ # text "my", "dog", "smells", :join => " "
25
+ # => "my dog smells"
26
+ #
27
+ # You may also pass a Promise as a parameter; every tag
28
+ # method now returns a Promise after emitting. This allows
29
+ # you to easily embed simple HTML formatting into a sentence, e.g.
30
+ #
31
+ # text "my", "dog", "smells", b("great!"), :join => " "
32
+ # => "my dog smells <b>great!</b>"
33
+ #
34
+ # (Yes, the initial call to +b+ emits "\&lt;b>great\&lt;/b>" to the output buffer;
35
+ # the Promise feature takes care of rewinding and rewriting the output
36
+ # buffer during the later call to +text+.)
37
+ #
38
+ def text(*values)
39
+ options = if values.last.is_a? Hash
40
+ values.pop
41
+ else
42
+ {}
43
+ end
44
+ delimiter = options[:join]
45
+
46
+ values.select{|value| value.is_a? Promise}.each do |promise|
47
+ # erase whatever the promises wrote already
48
+ promise._rewind
49
+ end
50
+
51
+ first = true
52
+ values.each do |value|
53
+ if !first and delimiter
54
+ output << h(delimiter)
55
+ end
56
+ first = false
57
+
58
+ case value
59
+ when AbstractWidget
60
+ # todo: better deprecation
61
+ raise "Don't pass a widget to the text method. Use the widget method instead."
62
+ when Promise
63
+ value._mark # so the promise's rewind won't erase anything
64
+ value._render # render the promise to the output stream again
65
+ # note: we could let the promise cache its first effort, but
66
+ # here I think it's better to optimize for memory over speed
67
+ else
68
+ output << h(value)
69
+ end
70
+ end
71
+ nil
72
+ end
73
+
74
+ # Returns text which will *not* be HTML-escaped.
75
+ def raw(value)
76
+ RawString.new(value.to_s)
77
+ end
78
+
79
+ # Emits text which will *not* be HTML-escaped. Same effect as text(raw(s))
80
+ def text!(value)
81
+ text raw(value)
82
+ end
83
+
84
+ alias rawtext text!
85
+
86
+ # Returns a copy of value with spaces replaced by non-breaking space characters.
87
+ # With no arguments, return a single non-breaking space.
88
+ # The output uses the escaping format '&#160;' since that works
89
+ # in both HTML and XML (as opposed to '&nbsp;' which only works in HTML).
90
+ def nbsp(value = " ")
91
+ raw(h(value).gsub(/ /,'&#160;'))
92
+ end
93
+
94
+ # Returns an HTML-escaped version of its parameter. Leaves the output
95
+ # string untouched. This method is idempotent: h(h(text)) will not
96
+ # double-escape text. This means that it is safe to do something like
97
+ # text(h("2<4")) -- it will produce "2&lt;4", not "2&amp;lt;4".
98
+ def h(content)
99
+ if content.respond_to?(:html_safe?) && content.html_safe?
100
+ content
101
+ else
102
+ raw(CGI.escapeHTML(content.to_s))
103
+ end
104
+ end
105
+
106
+ # Return a character given its unicode code point or unicode name.
107
+ def character(code_point_or_name)
108
+ if code_point_or_name.is_a?(Symbol)
109
+ require "erector/unicode"
110
+ found = Erector::CHARACTERS[code_point_or_name]
111
+ if found.nil?
112
+ raise "Unrecognized character #{code_point_or_name}"
113
+ end
114
+ raw("&#x#{sprintf '%x', found};")
115
+ elsif code_point_or_name.is_a?(Integer)
116
+ raw("&#x#{sprintf '%x', code_point_or_name};")
117
+ else
118
+ raise "Unrecognized argument to character: #{code_point_or_name}"
119
+ end
120
+ end
121
+
122
+ end
123
+ end
@@ -6,7 +6,7 @@ module Erector
6
6
  if !Erector.const_defined?(:VERSION)
7
7
  dir = File.dirname(__FILE__)
8
8
  version = YAML.load_file(File.expand_path("#{dir}/../../VERSION.yml"))
9
- VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
9
+ VERSION = [version[:major], version[:minor], version[:patch], version[:build]].compact.join('.')
10
10
  end
11
11
  end
12
12
 
@@ -1,5 +1,13 @@
1
+ require "erector/element"
2
+ require "erector/attributes"
3
+ require "erector/promise"
4
+ require "erector/text"
5
+ require "erector/tag"
6
+ require "erector/html_widget"
7
+ require "erector/needs"
8
+
1
9
  module Erector
2
-
10
+
3
11
  # A Widget is the center of the Erector universe.
4
12
  #
5
13
  # To create a widget, extend Erector::Widget and implement the +content+
@@ -36,19 +44,51 @@ module Erector
36
44
  #
37
45
  # This class extends AbstractWidget and includes several modules,
38
46
  # so be sure to check all of those places for API documentation for the
39
- # various methods of Widget. Also read the API Cheatsheet in the user guide
40
- # at http://erector.rubyforge.org/userguide#apicheatsheet
47
+ # various methods of Widget:
48
+ #
49
+ # * AbstractWidget
50
+ # * Element
51
+ # * Attributes
52
+ # * Text
53
+ # * Needs
54
+ # * Caching
55
+ # * Externals
56
+ # * AfterInitialize
41
57
  #
42
- # Now, seriously, after playing around a bit, go read the user guide. It's
43
- # fun!
44
- class Widget < AbstractWidget
45
- include Erector::HTML
46
- include Erector::Needs
47
- include Erector::Caching
48
- include Erector::Externals
49
- include Erector::Convenience
58
+ # * HTML
59
+ # * Convenience
60
+ # * JQuery
61
+ # * Sass
62
+ #
63
+ # Also read the API Cheatsheet in the user guide
64
+ # at http://erector.rubyforge.org/userguide#apicheatsheet
65
+ class Widget < HTMLWidget
66
+
67
+ # for some reason these need to be included in Widget and not AbstractWidget
68
+ include Needs
69
+ include Caching
70
+ include Externals
71
+
72
+ include HTML
73
+ include Convenience
50
74
  include Erector::JQuery
51
- include Erector::AfterInitialize
52
75
  include Erector::Sass if Object.const_defined?(:Sass)
76
+
77
+ # alias for AbstractWidget#render
78
+ def to_html(options = {})
79
+ raise "Erector::Widget#to_html takes an options hash, not a symbol. Try calling \"to_html(:content_method_name=> :#{options})\"" if options.is_a? Symbol
80
+ _render(options).to_s
81
+ end
82
+
83
+ # alias for #to_html
84
+ # @deprecated Please use {#to_html} instead
85
+ def to_s(*args)
86
+ unless defined? @@already_warned_to_s
87
+ $stderr.puts "Erector::Widget#to_s is deprecated. Please use #to_html instead. Called from #{caller.first}"
88
+ @@already_warned_to_s = true
89
+ end
90
+ to_html(*args)
91
+ end
92
+
53
93
  end
54
94
  end
@@ -7,14 +7,15 @@
7
7
  # declare it.
8
8
  #
9
9
  # The script and style declarations are accumulated at class load time, as
10
- # 'dependencies'. This technique allows all widgets to add their own requirements
11
- # to the page header without extra logic for declaring which pages include
12
- # which nested widgets. Fortunately, Page is now smart enough to figure out
13
- # which widgets were actually rendered during the body_content run, so it only
14
- # emits into its HEAD the dependencies that are relevant. If it misses some, or
15
- # if you want to add some extra dependencies -- for instance, styles that apply
16
- # to widgets that are rendered later via AJAX -- then return an array of those
17
- # widget classes in your subclass by overriding the #extra_widgets method.
10
+ # 'dependencies'. This technique allows all widgets to add their own
11
+ # requirements to the page header without extra logic for declaring which
12
+ # pages include which nested widgets. Fortunately, Page is now smart enough to
13
+ # figure out which widgets were actually rendered during the body_content run,
14
+ # so it only emits into its HEAD the dependencies that are relevant. If it
15
+ # misses some, or if you want to add some extra dependencies -- for instance,
16
+ # styles that apply to widgets that are rendered later via AJAX -- then return
17
+ # an array of those widget classes in your subclass by overriding the
18
+ # #extra_widgets method.
18
19
  #
19
20
  # If you want something to show up in the headers for just one page type
20
21
  # (subclass), then override #head_content, call super, and then emit it
@@ -93,8 +94,9 @@
93
94
  # end
94
95
  #
95
96
  # = Thoughts:
96
- # * It may be desirable to unify #js and #script, and #css and #style, and have the routine be
97
- # smart enough to analyze its parameter to decide whether to make it a file or a script.
97
+ # * It may be desirable to unify #js and #script, and #css and #style, and
98
+ # have the routine be smart enough to analyze its parameter to decide
99
+ # whether to make it a file or a script.
98
100
  #
99
101
  class Erector::Widgets::Page < Erector::InlineWidget
100
102
 
@@ -0,0 +1,131 @@
1
+ require 'erector/abstract_widget'
2
+ require 'erector/tag'
3
+ require 'erector/needs'
4
+
5
+ module Erector
6
+
7
+ # Abstract base class for XML Widgets and HTMLWidget.
8
+ # Declares "tags" which define methods that emit tags.
9
+ class XMLWidget < AbstractWidget
10
+ include Needs
11
+
12
+ def self.tag_named tag_name, checked = []
13
+ @tags ||= {}
14
+ @tags[tag_name] || begin
15
+ tag = nil
16
+ checked << self
17
+ taggy_ancestors = (ancestors - checked).select{|k| k.respond_to? :tag_named}
18
+ taggy_ancestors.each do |k|
19
+ tag = k.tag_named(tag_name, checked)
20
+ if tag
21
+ @tags[tag_name] = tag
22
+ break
23
+ end
24
+ end
25
+ tag
26
+ end
27
+ end
28
+
29
+ def self.tag *args
30
+ tag = Tag.new(*args)
31
+ @tags ||= {}
32
+ @tags[tag.name] = tag
33
+
34
+ if instance_methods.include?(tag.method_name.to_sym)
35
+ warn "method '#{tag.method_name}' is already defined; skipping #{caller[1]}"
36
+ return
37
+ end
38
+
39
+ if tag.self_closing?
40
+ self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
41
+ def #{tag.method_name}(*args, &block)
42
+ _empty_element('#{tag.name}', *args, &block)
43
+ end
44
+ SRC
45
+ else
46
+ self.class_eval(<<-SRC, __FILE__, __LINE__ + 1)
47
+ def #{tag.method_name}(*args, &block)
48
+ _element('#{tag.name}', *args, &block)
49
+ end
50
+
51
+ def #{tag.method_name}!(*args, &block)
52
+ _element('#{tag.name}', *(args.map{|a|raw(a)}), &block)
53
+ end
54
+ SRC
55
+ end
56
+ end
57
+
58
+ # Tags which are always self-closing
59
+ def self.self_closing_tags
60
+ @tags.values.select{|tag| tag.self_closing?}.map{|tag| tag.name}
61
+ end
62
+
63
+ # Tags which can contain other stuff
64
+ def self.full_tags
65
+ @tags.values.select{|tag| !tag.self_closing?}.map{|tag| tag.name}
66
+ end
67
+
68
+ def newliney?(tag_name)
69
+ tag = self.class.tag_named tag_name
70
+ if tag
71
+ tag.newliney?
72
+ else
73
+ true
74
+ end
75
+ end
76
+
77
+ # Emits an XML instruction, which looks like this: <?xml version=\"1.0\" encoding=\"UTF-8\" ?>
78
+ def instruct(attributes={:version => "1.0", :encoding => "UTF-8"})
79
+ output << raw("<?xml#{format_sorted(sort_for_xml_declaration(attributes))} ?>")
80
+ end
81
+
82
+ # Emits an XML/HTML comment (&lt;!-- ... --&gt;) surrounding +text+ and/or
83
+ # the output of +block+. see
84
+ # http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.2.4
85
+ #
86
+ # If +text+ is an Internet Explorer conditional comment condition such as
87
+ # "[if IE]", the output includes the opening condition and closing
88
+ # "[endif]". See http://www.quirksmode.org/css/condcom.html
89
+ #
90
+ # Since "Authors should avoid putting two or more adjacent hyphens inside
91
+ # comments," we emit a warning if you do that.
92
+ def comment(text = '')
93
+ puts "Warning: Authors should avoid putting two or more adjacent hyphens inside comments." if text =~ /--/
94
+
95
+ conditional = text =~ /\[if .*\]/
96
+
97
+ rawtext "<!--"
98
+ rawtext text
99
+ rawtext ">" if conditional
100
+
101
+ if block_given?
102
+ rawtext "\n"
103
+ yield
104
+ rawtext "\n"
105
+ end
106
+
107
+ rawtext "<![endif]" if conditional
108
+ rawtext "-->\n"
109
+ end
110
+
111
+ alias_method :to_xml, :render
112
+
113
+ protected
114
+
115
+ def sort_for_xml_declaration(attributes)
116
+ # correct order is "version, encoding, standalone" (XML 1.0 section 2.8).
117
+ # But we only try to put version before encoding for now.
118
+ stringized = []
119
+ attributes.each do |key, value|
120
+ stringized << [key.to_s, value]
121
+ end
122
+ stringized.sort{|a, b| b <=> a}
123
+ end
124
+
125
+ end
126
+
127
+ public
128
+
129
+ XmlWidget = XMLWidget
130
+
131
+ end