actionpack 1.9.1 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (123) hide show
  1. data/CHANGELOG +237 -0
  2. data/README +12 -12
  3. data/lib/action_controller.rb +17 -12
  4. data/lib/action_controller/assertions.rb +119 -67
  5. data/lib/action_controller/base.rb +184 -102
  6. data/lib/action_controller/benchmarking.rb +35 -6
  7. data/lib/action_controller/caching.rb +115 -58
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
  9. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
  10. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
  11. data/lib/action_controller/cgi_process.rb +23 -20
  12. data/lib/action_controller/components.rb +11 -2
  13. data/lib/action_controller/dependencies.rb +0 -5
  14. data/lib/action_controller/deprecated_redirects.rb +17 -0
  15. data/lib/action_controller/filters.rb +13 -9
  16. data/lib/action_controller/flash.rb +7 -7
  17. data/lib/action_controller/helpers.rb +1 -14
  18. data/lib/action_controller/layout.rb +40 -29
  19. data/lib/action_controller/macros/auto_complete.rb +52 -0
  20. data/lib/action_controller/macros/in_place_editing.rb +32 -0
  21. data/lib/action_controller/pagination.rb +44 -28
  22. data/lib/action_controller/request.rb +54 -40
  23. data/lib/action_controller/rescue.rb +8 -6
  24. data/lib/action_controller/routing.rb +77 -28
  25. data/lib/action_controller/scaffolding.rb +10 -14
  26. data/lib/action_controller/session/active_record_store.rb +36 -7
  27. data/lib/action_controller/session_management.rb +126 -0
  28. data/lib/action_controller/streaming.rb +14 -5
  29. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
  30. data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
  31. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
  32. data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
  33. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  34. data/lib/action_controller/test_process.rb +35 -17
  35. data/lib/action_controller/upload_progress.rb +52 -0
  36. data/lib/action_controller/url_rewriter.rb +21 -16
  37. data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
  38. data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
  39. data/lib/action_pack/version.rb +9 -0
  40. data/lib/action_view.rb +1 -1
  41. data/lib/action_view/base.rb +204 -60
  42. data/lib/action_view/compiled_templates.rb +70 -0
  43. data/lib/action_view/helpers/active_record_helper.rb +7 -3
  44. data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
  45. data/lib/action_view/helpers/capture_helper.rb +2 -10
  46. data/lib/action_view/helpers/date_helper.rb +21 -13
  47. data/lib/action_view/helpers/form_helper.rb +14 -10
  48. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  49. data/lib/action_view/helpers/form_tag_helper.rb +59 -25
  50. data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
  51. data/lib/action_view/helpers/javascript_helper.rb +68 -133
  52. data/lib/action_view/helpers/javascripts/controls.js +427 -165
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
  54. data/lib/action_view/helpers/javascripts/effects.js +766 -277
  55. data/lib/action_view/helpers/javascripts/prototype.js +906 -218
  56. data/lib/action_view/helpers/javascripts/slider.js +258 -0
  57. data/lib/action_view/helpers/number_helper.rb +4 -3
  58. data/lib/action_view/helpers/pagination_helper.rb +42 -27
  59. data/lib/action_view/helpers/tag_helper.rb +25 -11
  60. data/lib/action_view/helpers/text_helper.rb +119 -13
  61. data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
  62. data/lib/action_view/helpers/url_helper.rb +68 -21
  63. data/lib/action_view/partials.rb +17 -6
  64. data/lib/action_view/template_error.rb +19 -24
  65. data/rakefile +4 -3
  66. data/test/abstract_unit.rb +2 -1
  67. data/test/controller/action_pack_assertions_test.rb +62 -2
  68. data/test/controller/active_record_assertions_test.rb +5 -6
  69. data/test/controller/active_record_store_test.rb +23 -1
  70. data/test/controller/addresses_render_test.rb +4 -0
  71. data/test/controller/{base_tests.rb → base_test.rb} +4 -3
  72. data/test/controller/benchmark_test.rb +36 -0
  73. data/test/controller/caching_filestore.rb +22 -40
  74. data/test/controller/capture_test.rb +10 -1
  75. data/test/controller/cgi_test.rb +145 -23
  76. data/test/controller/components_test.rb +50 -0
  77. data/test/controller/custom_handler_test.rb +3 -3
  78. data/test/controller/fake_controllers.rb +24 -0
  79. data/test/controller/filters_test.rb +6 -6
  80. data/test/controller/flash_test.rb +6 -6
  81. data/test/controller/fragment_store_setting_test.rb +45 -0
  82. data/test/controller/helper_test.rb +1 -3
  83. data/test/controller/new_render_test.rb +119 -7
  84. data/test/controller/redirect_test.rb +11 -1
  85. data/test/controller/render_test.rb +34 -1
  86. data/test/controller/request_test.rb +14 -5
  87. data/test/controller/routing_test.rb +238 -42
  88. data/test/controller/send_file_test.rb +11 -10
  89. data/test/controller/session_management_test.rb +94 -0
  90. data/test/controller/test_test.rb +194 -5
  91. data/test/controller/url_rewriter_test.rb +46 -0
  92. data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
  93. data/test/fixtures/layouts/yield.rhtml +2 -0
  94. data/test/fixtures/multipart/binary_file +0 -0
  95. data/test/fixtures/multipart/large_text_file +10 -0
  96. data/test/fixtures/multipart/mixed_files +0 -0
  97. data/test/fixtures/multipart/single_parameter +5 -0
  98. data/test/fixtures/multipart/text_file +10 -0
  99. data/test/fixtures/test/_customer_greeting.rhtml +1 -0
  100. data/test/fixtures/test/_hash_object.rhtml +1 -0
  101. data/test/fixtures/test/_person.rhtml +2 -0
  102. data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
  103. data/test/fixtures/test/content_for.rhtml +2 -0
  104. data/test/fixtures/test/potential_conflicts.rhtml +4 -0
  105. data/test/template/active_record_helper_test.rb +15 -8
  106. data/test/template/asset_tag_helper_test.rb +40 -16
  107. data/test/template/compiled_templates_tests.rb +63 -0
  108. data/test/template/date_helper_test.rb +80 -4
  109. data/test/template/form_helper_test.rb +48 -42
  110. data/test/template/form_options_helper_test.rb +40 -40
  111. data/test/template/form_tag_helper_test.rb +21 -15
  112. data/test/template/java_script_macros_helper_test.rb +56 -0
  113. data/test/template/javascript_helper_test.rb +70 -47
  114. data/test/template/number_helper_test.rb +2 -0
  115. data/test/template/tag_helper_test.rb +9 -0
  116. data/test/template/text_helper_test.rb +146 -1
  117. data/test/template/upload_progress_helper_testx.rb +11 -147
  118. data/test/template/url_helper_test.rb +90 -22
  119. data/test/testing_sandbox.rb +26 -0
  120. metadata +37 -7
  121. data/lib/action_controller/auto_complete.rb +0 -47
  122. data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
  123. data/lib/action_controller/session.rb +0 -14
@@ -61,20 +61,20 @@ module ActionController #:nodoc:
61
61
  @performed_render = false
62
62
 
63
63
  if options[:stream]
64
- render :text => Proc.new {
64
+ render :text => Proc.new { |response, output|
65
65
  logger.info "Streaming file #{path}" unless logger.nil?
66
66
  len = options[:buffer_size] || 4096
67
67
  File.open(path, 'rb') do |file|
68
- if $stdout.respond_to?(:syswrite)
68
+ if output.respond_to?(:syswrite)
69
69
  begin
70
70
  while true
71
- $stdout.syswrite file.sysread(len)
71
+ output.syswrite(file.sysread(len))
72
72
  end
73
73
  rescue EOFError
74
74
  end
75
75
  else
76
76
  while buf = file.read(len)
77
- $stdout.write buf
77
+ output.write(buf)
78
78
  end
79
79
  end
80
80
  end
@@ -120,6 +120,7 @@ module ActionController #:nodoc:
120
120
  end
121
121
 
122
122
  disposition = options[:disposition].dup || 'attachment'
123
+
123
124
  disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
124
125
 
125
126
  @headers.update(
@@ -127,7 +128,15 @@ module ActionController #:nodoc:
127
128
  'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
128
129
  'Content-Disposition' => disposition,
129
130
  'Content-Transfer-Encoding' => 'binary'
130
- );
131
+ )
132
+
133
+ # Fix a problem with IE 6.0 on opening downloaded files:
134
+ # If Cache-Control: no-cache is set (which Rails does by default),
135
+ # IE removes the file it just downloaded from its cache immediately
136
+ # after it displays the "open/save" dialog, which means that if you
137
+ # hit "open" the file isn't there anymore when the application that
138
+ # is called for handling the download is run, so let's workaround that
139
+ @headers['Cache-Control'] = 'private' if @headers['Cache-Control'] == 'no-cache'
131
140
  end
132
141
  end
133
142
  end
@@ -5,7 +5,7 @@
5
5
  <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre>
6
6
  <% end %>
7
7
 
8
- <% if defined?(Breakpoint) %>
8
+ <% if false %>
9
9
  <br /><br />
10
10
  <% begin %>
11
11
  <%= form_tag(@request.request_uri, "method" => @request.method) %>
@@ -0,0 +1,24 @@
1
+ <%
2
+ traces = [
3
+ ["Application Trace", @exception.application_backtrace],
4
+ ["Framework Trace", @exception.framework_backtrace],
5
+ ["Full Trace", @exception.clean_backtrace]
6
+ ]
7
+ names = traces.collect {|name, trace| name}
8
+ %>
9
+
10
+ <div id="traces">
11
+ <% names.each do |name| -%>
12
+ <%
13
+ show = "document.getElementById('#{name.gsub /\s/, '-'}').style.display='block';"
14
+ hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub /\s/, '-'}').style.display='none';"}
15
+ %>
16
+ <a href="#" onclick="<%= hide %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %>
17
+ <% end -%>
18
+
19
+ <% traces.each do |name, trace| -%>
20
+ <div id="<%= name.gsub /\s/, '-' %>" style="display: <%= name == "Application Trace" ? 'block' : 'none' %>;">
21
+ <pre><code><%= trace.join "\n" %></code></pre>
22
+ </div>
23
+ <% end -%>
24
+ </div>
@@ -1,20 +1,9 @@
1
- <%
2
- clean_backtrace = @exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }
3
- app_trace = clean_backtrace.reject { |line| line =~ /(vendor|dispatch|ruby)/ }
4
- framework_trace = clean_backtrace - app_trace
5
- %>
6
-
7
1
  <h1>
8
2
  <%=h @exception.class.to_s %> in
9
3
  <%=h (@request.parameters["controller"] || "<controller not set>").capitalize %>#<%=h @request.parameters["action"] || "<action not set>" %>
10
4
  </h1>
11
- <pre><%=h Object.const_defined?(:RAILS_ROOT) ? @exception.message.gsub(RAILS_ROOT, "") : @exception.message %></pre>
12
-
13
- <% unless app_trace.empty? %><pre><code><%=h app_trace.join("\n") %></code></pre><% end %>
5
+ <pre><%=h @exception.clean_message %></pre>
14
6
 
15
- <% unless framework_trace.empty? %>
16
- <a href="#" onclick="document.getElementById('framework_trace').style.display='block'; return false;">Show framework trace</a>
17
- <pre id="framework_trace" style="display:none"><code><%=h framework_trace.join("\n") %></code></pre>
18
- <% end %>
7
+ <%= render_file(@rescues_path + "/_trace.rhtml", false) %>
19
8
 
20
9
  <%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
@@ -13,7 +13,9 @@
13
13
 
14
14
  <p><%=h @exception.sub_template_message %></p>
15
15
 
16
- <a href="#" onclick="document.getElementById('framework_trace').style.display='block'">Show template trace</a>
17
- <pre id="framework_trace" style="display:none"><code><%=h @exception.original_exception.backtrace.collect { |line| Object.const_defined?(:RAILS_ROOT) ? line.gsub(RAILS_ROOT, "") : line }.join("\n") %></code></pre>
16
+ <% @real_exception = @exception
17
+ @exception = @exception.original_exception || @exception %>
18
+ <%= render_file(@rescues_path + "/_trace.rhtml", false) %>
19
+ <% @exception = @real_exception %>
18
20
 
19
21
  <%= render_file(@rescues_path + "/_request_and_response.rhtml", false) %>
@@ -14,7 +14,7 @@
14
14
  <% end %>
15
15
  <td><%= link_to "Show", :action => "show#{@scaffold_suffix}", :id => entry %></td>
16
16
  <td><%= link_to "Edit", :action => "edit#{@scaffold_suffix}", :id => entry %></td>
17
- <td><%= link_to "Destroy", :action => "destroy#{@scaffold_suffix}", :id => entry, :confirm => "Are you sure?" %></td>
17
+ <td><%= link_to "Destroy", {:action => "destroy#{@scaffold_suffix}", :id => entry}, {:confirm => "Are you sure?"} %></td>
18
18
  </tr>
19
19
  <% end %>
20
20
  </table>
@@ -11,10 +11,19 @@ module ActionController #:nodoc:
11
11
  def process_test(request) #:nodoc:
12
12
  process(request, TestResponse.new)
13
13
  end
14
+
15
+ def process_with_test(*args)
16
+ returning process_without_test(*args) do
17
+ add_variables_to_assigns
18
+ end
19
+ end
20
+
21
+ alias_method :process_without_test, :process
22
+ alias_method :process, :process_with_test
14
23
  end
15
24
 
16
25
  class TestRequest < AbstractRequest #:nodoc:
17
- attr_accessor :cookies
26
+ attr_accessor :cookies, :session_options
18
27
  attr_accessor :query_parameters, :request_parameters, :path, :session, :env
19
28
  attr_accessor :host
20
29
 
@@ -35,6 +44,7 @@ module ActionController #:nodoc:
35
44
 
36
45
  def port=(number)
37
46
  @env["SERVER_PORT"] = number.to_i
47
+ @port_as_int = nil
38
48
  end
39
49
 
40
50
  def action=(action_name)
@@ -59,6 +69,10 @@ module ActionController #:nodoc:
59
69
  @env['REMOTE_ADDR'] = addr
60
70
  end
61
71
 
72
+ def remote_addr
73
+ @env['REMOTE_ADDR']
74
+ end
75
+
62
76
  def request_uri
63
77
  @request_uri || super()
64
78
  end
@@ -102,6 +116,7 @@ module ActionController #:nodoc:
102
116
  @request_uri = "/"
103
117
  self.remote_addr = "0.0.0.0"
104
118
  @env["SERVER_PORT"] = 80
119
+ @env['REQUEST_METHOD'] = "GET"
105
120
  end
106
121
  end
107
122
 
@@ -110,6 +125,15 @@ module ActionController #:nodoc:
110
125
  def response_code
111
126
  headers['Status'][0,3].to_i rescue 0
112
127
  end
128
+
129
+ # returns a String to ensure compatibility with Net::HTTPResponse
130
+ def code
131
+ headers['Status'].to_s.split(' ')[0]
132
+ end
133
+
134
+ def message
135
+ headers['Status'].to_s.split(' ',2)[1]
136
+ end
113
137
 
114
138
  # was the response successful?
115
139
  def success?
@@ -255,13 +279,14 @@ module Test
255
279
  private
256
280
  # execute the request and set/volley the response
257
281
  def process(action, parameters = nil, session = nil, flash = nil)
258
- @request.recycle!
259
-
260
- # Sanity check for required instance variables so we can give an understandable error message.
282
+ # Sanity check for required instance variables so we can give an
283
+ # understandable error message.
261
284
  %w(controller request response).each do |iv_name|
262
- assert_not_nil instance_variable_get("@#{iv_name}"), "@#{iv_name} is nil: make sure you set it in your test's setup method."
285
+ raise "@#{iv_name} is nil: make sure you set it in your test's setup method." if instance_variable_get("@#{iv_name}").nil?
263
286
  end
264
287
 
288
+ @request.recycle!
289
+
265
290
  @html_document = nil
266
291
  @request.env['REQUEST_METHOD'] ||= "GET"
267
292
  @request.action = action.to_s
@@ -277,9 +302,9 @@ module Test
277
302
 
278
303
  # execute the request simulating a specific http method and set/volley the response
279
304
  %w( get post put delete head ).each do |method|
280
- class_eval <<-EOV
305
+ class_eval <<-EOV, __FILE__, __LINE__
281
306
  def #{method}(action, parameters = nil, session = nil, flash = nil)
282
- @request.env['REQUEST_METHOD'] = "#{method.upcase}"
307
+ @request.env['REQUEST_METHOD'] = "#{method.upcase}" if @request
283
308
  process(action, parameters, session, flash)
284
309
  end
285
310
  EOV
@@ -287,7 +312,9 @@ module Test
287
312
 
288
313
  def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
289
314
  @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
290
- self.send(request_method, action, parameters, session, flash)
315
+ returning self.send(request_method, action, parameters, session, flash) do
316
+ @request.env.delete 'HTTP_X_REQUESTED_WITH'
317
+ end
291
318
  end
292
319
  alias xhr :xml_http_request
293
320
 
@@ -334,7 +361,6 @@ module Test
334
361
  end
335
362
 
336
363
  def html_document
337
- require_html_scanner
338
364
  @html_document ||= HTML::Document.new(@response.body)
339
365
  end
340
366
 
@@ -346,14 +372,6 @@ module Test
346
372
  html_document.find_all(conditions)
347
373
  end
348
374
 
349
- def require_html_scanner
350
- return true if defined?(HTML::Document)
351
- require 'html/document'
352
- rescue LoadError
353
- $:.unshift File.dirname(__FILE__) + "/vendor/html-scanner"
354
- require 'html/document'
355
- end
356
-
357
375
  def method_missing(selector, *args)
358
376
  return @controller.send(selector, *args) if ActionController::Routing::NamedRoutes::Helpers.include?(selector)
359
377
  return super
@@ -13,6 +13,9 @@ module ActionController #:nodoc:
13
13
  # fully on all the systems that is a part of your environment. Consider this an extended
14
14
  # preview.
15
15
  #
16
+ # To enable this module, add <tt>ActionController::Base.enable_upload_progress</tt> to your
17
+ # config/environment.rb file.
18
+ #
16
19
  # == Action Pack Upload Progress for multipart uploads
17
20
  #
18
21
  # The UploadProgress module aids in the process of viewing an Ajax driven
@@ -131,6 +134,55 @@ module ActionController #:nodoc:
131
134
  # def custom_status
132
135
  # render :inline => '<%= upload_progress_status %> <div>Updated at <%= Time.now %></div>', :layout => false
133
136
  # end
137
+ #
138
+ # ==== Environment checklist
139
+ #
140
+ # This is an experimental feature that requires a specific webserver environment. Use the following checklist
141
+ # to confirm that you have an environment that supports upload progress.
142
+ #
143
+ # ===== Ruby:
144
+ #
145
+ # * Running the command `ruby -v` should print "ruby 1.8.2 (2004-12-25)" or older
146
+ #
147
+ # ===== Web server:
148
+ #
149
+ # * Apache 1.3, Apache 2.0 or Lighttpd *1.4* (need to build lighttpd from CVS)
150
+ #
151
+ # ===== FastCGI bindings:
152
+ #
153
+ # * > 0.8.6 and must be the compiled C version of the bindings
154
+ # * The command `ruby -e "p require('fcgi.so')"` should print "true"
155
+ #
156
+ # ===== Apache/Lighttpd FastCGI directives:
157
+ #
158
+ # * You must allow more than one FCGI server process to allow concurrent requests.
159
+ # * If there is only a single FCGI process you will not get the upload status updates.
160
+ # * You can check this by taking a look for running FCGI servers in your process list during a progress upload.
161
+ # * Apache directive: FastCGIConfig -minProcesses 2
162
+ # * Lighttpd directives taken from config/lighttpd.conf (min-procs):
163
+ #
164
+ # fastcgi.server = (
165
+ # ".fcgi" => (
166
+ # "APP_NAME" => (
167
+ # "socket" => "/tmp/APP_NAME1.socket",
168
+ # "bin-path" => "RAILS_ROOT/public/dispatch.fcgi",
169
+ # "min-procs" => 2
170
+ # )
171
+ # )
172
+ # )
173
+ #
174
+ # ===== config/environment.rb:
175
+ #
176
+ # * Add the following line to your config/environment.rb and restart your web server.
177
+ # * <tt>ActionController::Base.enable_upload_progress</tt>
178
+ #
179
+ # ===== Development log:
180
+ #
181
+ # * When the upload progress is enabled by you will find something the following lines:
182
+ # * "Multipart upload with progress (id: 1, size: 85464)"
183
+ # * "Finished processing multipart upload in 0.363729s"
184
+ # * If you are properly running multiple FCGI processes, then you will see multiple entries for rendering the "upload_status" action before the "Finish processing..." log entry. This is a *good thing* :)
185
+ #
134
186
  module UploadProgress
135
187
  def self.append_features(base) #:nodoc:
136
188
  super
@@ -20,8 +20,10 @@ module ActionController
20
20
  private
21
21
  def rewrite_url(path, options)
22
22
  rewritten_url = ""
23
- rewritten_url << (options[:protocol] || @request.protocol) unless options[:only_path]
24
- rewritten_url << (options[:host] || @request.host_with_port) unless options[:only_path]
23
+ unless options[:only_path]
24
+ rewritten_url << (options[:protocol] || @request.protocol)
25
+ rewritten_url << (options[:host] || @request.host_with_port)
26
+ end
25
27
 
26
28
  rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root]
27
29
  rewritten_url << path
@@ -33,32 +35,35 @@ module ActionController
33
35
 
34
36
  def rewrite_path(options)
35
37
  options = options.symbolize_keys
36
- options.update((options[:params] || {}).symbolize_keys)
37
- RESERVED_OPTIONS.each {|k| options.delete k}
38
- path, extras = Routing::Routes.generate(options, @request)
39
-
40
- if extras[:overwrite_params]
41
- params_copy = @request.parameters.reject { |k,v| %w(controller action).include? k }
42
- params_copy.update extras[:overwrite_params]
43
- extras.delete(:overwrite_params)
44
- extras.update(params_copy)
38
+ options.update(options[:params].symbolize_keys) if options[:params]
39
+ if (overwrite = options.delete(:overwrite_params))
40
+ options.update(@parameters.symbolize_keys)
41
+ options.update(overwrite)
45
42
  end
43
+ RESERVED_OPTIONS.each {|k| options.delete k}
44
+ path, extra_keys = Routing::Routes.generate(options.dup, @request) # Warning: Routes will mutate and violate the options hash
46
45
 
47
- path << build_query_string(extras) unless extras.empty?
46
+ path << build_query_string(options, extra_keys) unless extra_keys.empty?
48
47
 
49
48
  path
50
49
  end
51
50
 
52
51
  # Returns a query string with escaped keys and values from the passed hash. If the passed hash contains an "id" it'll
53
52
  # be added as a path element instead of a regular parameter pair.
54
- def build_query_string(hash)
53
+ def build_query_string(hash, only_keys = nil)
55
54
  elements = []
56
55
  query_string = ""
56
+
57
+ only_keys ||= hash.keys
57
58
 
58
- hash.each do |key, value|
59
+ only_keys.each do |key|
60
+ value = hash[key]
59
61
  key = CGI.escape key.to_s
60
- key << '[]' if value.class == Array
61
- value = [ value ] unless value.class == Array
62
+ if value.class == Array
63
+ key << '[]'
64
+ else
65
+ value = [ value ]
66
+ end
62
67
  value.each { |val| elements << "#{key}=#{Routing.extract_parameter_value(val)}" }
63
68
  end
64
69
 
@@ -11,7 +11,7 @@ module HTML #:nodoc:
11
11
  attr_reader :root
12
12
 
13
13
  # Create a new Document from the given text.
14
- def initialize(text, strict=false)
14
+ def initialize(text, strict=false, xml=false)
15
15
  tokenizer = Tokenizer.new(text)
16
16
  @root = Node.new(nil)
17
17
  node_stack = [ @root ]
@@ -38,7 +38,7 @@ ignoring attempt to close #{node_stack.last.name} with #{node.name}
38
38
  EOF
39
39
  strict ? raise(msg) : warn(msg)
40
40
  end
41
- elsif !node.childless? && node.closing != :close
41
+ elsif !node.childless?(xml) && node.closing != :close
42
42
  node_stack.push node
43
43
  end
44
44
  end
@@ -122,6 +122,18 @@ module HTML #:nodoc:
122
122
  def validate_conditions(conditions)
123
123
  Conditions === conditions ? conditions : Conditions.new(conditions)
124
124
  end
125
+
126
+ def ==(node)
127
+ return false unless self.class == node.class && children.size == node.children.size
128
+
129
+ equivalent = true
130
+
131
+ children.size.times do |i|
132
+ equivalent &&= children[i] == node.children[i]
133
+ end
134
+
135
+ equivalent
136
+ end
125
137
 
126
138
  class <<self
127
139
  def parse(parent, line, pos, content, strict=true)
@@ -238,6 +250,11 @@ module HTML #:nodoc:
238
250
  nil
239
251
  end
240
252
  end
253
+
254
+ def ==(node)
255
+ return false unless super
256
+ content == node.content
257
+ end
241
258
  end
242
259
 
243
260
  # A Tag is any node that represents markup. It may be an opening tag, a
@@ -271,7 +288,8 @@ module HTML #:nodoc:
271
288
  end
272
289
 
273
290
  # Returns non-+nil+ if this tag can contain child nodes.
274
- def childless?
291
+ def childless?(xml = false)
292
+ return false if xml && @closing.nil?
275
293
  !@closing.nil? ||
276
294
  @name =~ /^(img|br|hr|link|meta|area|base|basefont|
277
295
  col|frame|input|isindex|param)$/ox
@@ -417,7 +435,11 @@ module HTML #:nodoc:
417
435
 
418
436
  # count children
419
437
  if opts = conditions[:children]
420
- matches = children
438
+ matches = children.select do |c|
439
+ c.match(/./) or
440
+ (c.kind_of?(HTML::Tag) and (c.closing == :self or ! c.childless?))
441
+ end
442
+
421
443
  matches = matches.select { |c| c.match(opts[:only]) } if opts[:only]
422
444
  opts.each do |key, value|
423
445
  next if key == :only
@@ -464,8 +486,13 @@ module HTML #:nodoc:
464
486
  true
465
487
  end
466
488
 
489
+ def ==(node)
490
+ return false unless super
491
+ return false unless closing == node.closing && self.name == node.name
492
+ attributes == node.attributes
493
+ end
494
+
467
495
  private
468
-
469
496
  # Match the given value to the given condition.
470
497
  def match_condition(value, condition)
471
498
  case condition