merb 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/README +21 -14
  2. data/Rakefile +157 -108
  3. data/SVN_REVISION +1 -0
  4. data/app_generators/merb/templates/Rakefile +20 -4
  5. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
  6. data/app_generators/merb/templates/config/boot.rb +1 -1
  7. data/app_generators/merb/templates/config/dependencies.rb +3 -3
  8. data/app_generators/merb/templates/config/merb.yml +5 -0
  9. data/app_generators/merb/templates/config/merb_init.rb +3 -3
  10. data/app_generators/merb/templates/script/destroy +3 -0
  11. data/app_generators/merb/templates/script/generate +1 -1
  12. data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
  13. data/app_generators/merb/templates/test/test_helper.rb +1 -1
  14. data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
  15. data/bin/merb +1 -3
  16. data/lib/merb.rb +144 -76
  17. data/lib/merb/abstract_controller.rb +6 -5
  18. data/lib/merb/assets.rb +119 -0
  19. data/lib/merb/boot_loader.rb +217 -0
  20. data/lib/merb/caching.rb +1 -1
  21. data/lib/merb/caching/action_cache.rb +1 -1
  22. data/lib/merb/caching/fragment_cache.rb +1 -1
  23. data/lib/merb/caching/store/file_cache.rb +1 -1
  24. data/lib/merb/config.rb +290 -0
  25. data/lib/merb/controller.rb +5 -5
  26. data/lib/merb/core_ext/get_args.rb +1 -0
  27. data/lib/merb/core_ext/hash.rb +182 -169
  28. data/lib/merb/core_ext/kernel.rb +57 -26
  29. data/lib/merb/dispatcher.rb +6 -6
  30. data/lib/merb/drb_server.rb +1 -1
  31. data/lib/merb/generators/merb_generator_helpers.rb +7 -6
  32. data/lib/merb/logger.rb +1 -1
  33. data/lib/merb/mail_controller.rb +3 -4
  34. data/lib/merb/mailer.rb +2 -2
  35. data/lib/merb/mixins/basic_authentication.rb +2 -2
  36. data/lib/merb/mixins/controller.rb +1 -1
  37. data/lib/merb/mixins/general_controller.rb +13 -20
  38. data/lib/merb/mixins/inline_partial.rb +32 -0
  39. data/lib/merb/mixins/render.rb +3 -3
  40. data/lib/merb/mixins/responder.rb +1 -1
  41. data/lib/merb/mixins/view_context.rb +159 -33
  42. data/lib/merb/mongrel_handler.rb +9 -9
  43. data/lib/merb/plugins.rb +1 -1
  44. data/lib/merb/request.rb +25 -1
  45. data/lib/merb/router.rb +264 -226
  46. data/lib/merb/server.rb +66 -560
  47. data/lib/merb/session/cookie_store.rb +14 -13
  48. data/lib/merb/session/mem_cache_session.rb +20 -10
  49. data/lib/merb/session/memory_session.rb +21 -11
  50. data/lib/merb/template.rb +2 -2
  51. data/lib/merb/template/erubis.rb +3 -33
  52. data/lib/merb/template/haml.rb +8 -3
  53. data/lib/merb/test/fake_request.rb +8 -3
  54. data/lib/merb/test/helper.rb +66 -22
  55. data/lib/merb/test/rspec.rb +9 -155
  56. data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
  57. data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
  58. data/lib/merb/upload_handler.rb +2 -1
  59. data/lib/merb/version.rb +38 -3
  60. data/lib/merb/view_context.rb +1 -2
  61. data/lib/tasks/merb.rake +11 -11
  62. data/merb_generators/part_controller/USAGE +5 -0
  63. data/merb_generators/part_controller/part_controller_generator.rb +27 -0
  64. data/merb_generators/part_controller/templates/controller.rb +8 -0
  65. data/merb_generators/part_controller/templates/helper.rb +5 -0
  66. data/merb_generators/part_controller/templates/index.html.erb +3 -0
  67. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
  68. data/script/destroy +14 -0
  69. data/script/generate +14 -0
  70. data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
  71. data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
  72. data/spec/fixtures/models/router_spec_models.rb +10 -0
  73. data/spec/merb/abstract_controller_spec.rb +2 -2
  74. data/spec/merb/assets_spec.rb +207 -0
  75. data/spec/merb/caching_spec.rb +2 -2
  76. data/spec/merb/controller_spec.rb +7 -2
  77. data/spec/merb/cookie_store_spec.rb +1 -1
  78. data/spec/merb/core_ext/class_spec.rb +97 -0
  79. data/spec/merb/core_ext/enumerable_spec.rb +27 -0
  80. data/spec/merb/core_ext/hash_spec.rb +251 -0
  81. data/spec/merb/core_ext/inflector_spec.rb +34 -0
  82. data/spec/merb/core_ext/kernel_spec.rb +25 -0
  83. data/spec/merb/core_ext/numeric_spec.rb +26 -0
  84. data/spec/merb/core_ext/object_spec.rb +47 -0
  85. data/spec/merb/core_ext/string_spec.rb +22 -0
  86. data/spec/merb/core_ext/symbol_spec.rb +7 -0
  87. data/spec/merb/dependency_spec.rb +22 -0
  88. data/spec/merb/dispatch_spec.rb +23 -12
  89. data/spec/merb/fake_request_spec.rb +8 -0
  90. data/spec/merb/generator_spec.rb +140 -21
  91. data/spec/merb/handler_spec.rb +5 -5
  92. data/spec/merb/mail_controller_spec.rb +3 -3
  93. data/spec/merb/render_spec.rb +1 -1
  94. data/spec/merb/responder_spec.rb +3 -3
  95. data/spec/merb/router_spec.rb +260 -191
  96. data/spec/merb/server_spec.rb +5 -5
  97. data/spec/merb/upload_handler_spec.rb +7 -0
  98. data/spec/merb/version_spec.rb +33 -0
  99. data/spec/merb/view_context_spec.rb +217 -59
  100. data/spec/spec_generator_helper.rb +15 -0
  101. data/spec/spec_helper.rb +5 -3
  102. data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
  103. metadata +32 -7
  104. data/lib/merb/caching/store/memcache.rb +0 -20
  105. data/lib/merb/mixins/form_control.rb +0 -332
  106. data/lib/patch +0 -69
  107. data/spec/merb/core_ext_spec.rb +0 -464
  108. data/spec/merb/form_control_mixin_spec.rb +0 -431
@@ -18,7 +18,7 @@ module Merb
18
18
  #
19
19
  # === Session Store
20
20
  #
21
- # The session store is set in MERB_ROOT/config/merb.yml :
21
+ # The session store is set in Merb.root/config/merb.yml :
22
22
  #
23
23
  # :session_store: your_store
24
24
  #
@@ -83,14 +83,14 @@ module Merb
83
83
 
84
84
  def set_dispatch_variables(request, response, status, headers)
85
85
  if request.params.key?(_session_id_key)
86
- if Merb::Server.config[:session_id_cookie_only]
86
+ if Merb::Config[:session_id_cookie_only]
87
87
  # This condition allows for certain controller/action paths to allow
88
88
  # a session ID to be passed in a query string. This is needed for
89
89
  # Flash Uploads to work since flash will not pass a Session Cookie
90
90
  # Recommend running session.regenerate after any controller taking
91
91
  # advantage of this in case someone is attempting a session fixation
92
92
  # attack
93
- if Merb::Server.config[:query_string_whitelist].include?("#{request.controller_name}/#{request.action}")
93
+ if Merb::Config[:query_string_whitelist].include?("#{request.controller_name}/#{request.action}")
94
94
  # FIXME to use routes not controller and action names -----^
95
95
  request.cookies[_session_id_key] = request.params[_session_id_key]
96
96
  end
@@ -105,7 +105,7 @@ module Merb
105
105
  end
106
106
 
107
107
  def dispatch(action=:index)
108
- start = Time.now
108
+ start = Time.now
109
109
  if self.class.callable_actions[action.to_s]
110
110
  params[:action] ||= action
111
111
  setup_session
@@ -115,7 +115,7 @@ module Merb
115
115
  raise ActionNotFound, "Action '#{action}' was not found in #{self.class}"
116
116
  end
117
117
  @_benchmarks[:action_time] = Time.now - start
118
- MERB_LOGGER.info("Time spent in #{self.class}##{action} action: #{@_benchmarks[:action_time]} seconds")
118
+ Merb.logger.info("Time spent in #{self.class}##{action} action: #{@_benchmarks[:action_time]} seconds")
119
119
  end
120
120
 
121
121
  # Accessor for @_body. Please use body and never @body directly.
@@ -24,6 +24,7 @@ begin
24
24
 
25
25
  def get_args
26
26
  arg_node = deep_array_node(:args)
27
+ return nil unless arg_node
27
28
  args = arg_node.arg_nodes
28
29
  default_node = arg_node.deep_array_node(:block)
29
30
  return args unless default_node
@@ -1,139 +1,154 @@
1
- #require 'hpricot'
1
+ # require 'hpricot'
2
2
 
3
3
  class Hash
4
-
5
4
  class << self
6
- # Converts valid XML into a Ruby Hash structure.
7
- # <tt>xml</tt>:: A string representation of valid XML
8
- #
9
- # == Typecasting
10
- # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of
11
- # <tt>integer</tt>::
12
- # <tt>boolean</tt>:: anything other than "true" evaluates to false
13
- # <tt>datetime</tt>:: Returns a Time object. See +Time+ documentation for valid Time strings
14
- # <tt>date</tt>:: Returns a Date object. See +Date+ documentation for valid Date strings
15
- #
16
- # Keys are automatically converted to +snake_case+
17
- #
18
- # == Caveats
19
- # * Mixed content tags are assumed to be text and any xml tags are kept as a String
20
- # * Any attributes other than type on a node containing a text node will be discarded
21
- #
22
- # == Examples
23
- #
24
- # === Standard
25
- # <user gender='m'>
26
- # <age type='integer'>35</age>
27
- # <name>Home Simpson</name>
28
- # <dob type='date'>1988-01-01</dob>
29
- # <joined-at type='datetime'>2000-04-28 23:01</joined-at>
30
- # <is-cool type='boolean'>true</is-cool>
31
- # </user>
32
- #
33
- # evaluates to
34
- #
35
- # { "user" =>
36
- # { "gender" => "m",
37
- # "age" => 35,
38
- # "name" => "Home Simpson",
39
- # "dob" => DateObject( 1998-01-01 ),
40
- # "joined_at" => TimeObject( 2000-04-28 23:01),
41
- # "is_cool" => true
42
- # }
43
- # }
44
- #
45
- # === Mixed Content
46
- # <story>
47
- # A Quick <em>brown</em> Fox
48
- # </story>
49
- #
50
- # evaluates to
51
- # { "story" => "A Quick <em>brown</em> Fox" }
52
- #
53
- # === Attributes other than type on a node containing text
54
- # <story is-good='fasle'>
55
- # A Quick <em>brown</em> Fox
56
- # </story>
57
- #
58
- # evaluates to
59
- # { "story" => "A Quick <em>brown</em> Fox" }
60
- #
61
- # <bicep unit='inches' type='integer'>60</bicep>
62
- #
63
- # evaluates with a typecast to an integer. But ignores the unit attribute
64
- # { "bicep" => 60 }
65
-
66
- def from_xml( xml )
67
- ToHashParser.from_xml(xml)
68
- end
5
+ # Converts valid XML into a Ruby Hash structure.
6
+ # <tt>xml</tt>:: A string representation of valid XML
7
+ #
8
+ # == Typecasting
9
+ # Typecasting is performed on elements that have a "<tt>type</tt>" attribute of
10
+ # <tt>integer</tt>::
11
+ # <tt>boolean</tt>:: anything other than "true" evaluates to false
12
+ # <tt>datetime</tt>:: Returns a Time object. See +Time+ documentation for valid Time strings
13
+ # <tt>date</tt>:: Returns a Date object. See +Date+ documentation for valid Date strings
14
+ #
15
+ # Keys are automatically converted to +snake_case+
16
+ #
17
+ # == Caveats
18
+ # * Mixed content tags are assumed to be text and any xml tags are kept as a String
19
+ # * Any attributes other than type on a node containing a text node will be discarded
20
+ #
21
+ # == Examples
22
+ #
23
+ # === Standard
24
+ # <user gender='m'>
25
+ # <age type='integer'>35</age>
26
+ # <name>Home Simpson</name>
27
+ # <dob type='date'>1988-01-01</dob>
28
+ # <joined-at type='datetime'>2000-04-28 23:01</joined-at>
29
+ # <is-cool type='boolean'>true</is-cool>
30
+ # </user>
31
+ #
32
+ # evaluates to
33
+ #
34
+ # { "user" =>
35
+ # { "gender" => "m",
36
+ # "age" => 35,
37
+ # "name" => "Home Simpson",
38
+ # "dob" => DateObject( 1998-01-01 ),
39
+ # "joined_at" => TimeObject( 2000-04-28 23:01),
40
+ # "is_cool" => true
41
+ # }
42
+ # }
43
+ #
44
+ # === Mixed Content
45
+ # <story>
46
+ # A Quick <em>brown</em> Fox
47
+ # </story>
48
+ #
49
+ # evaluates to
50
+ # { "story" => "A Quick <em>brown</em> Fox" }
51
+ #
52
+ # === Attributes other than type on a node containing text
53
+ # <story is-good='fasle'>
54
+ # A Quick <em>brown</em> Fox
55
+ # </story>
56
+ #
57
+ # evaluates to
58
+ # { "story" => "A Quick <em>brown</em> Fox" }
59
+ #
60
+ # <bicep unit='inches' type='integer'>60</bicep>
61
+ #
62
+ # evaluates with a typecast to an integer. But ignores the unit attribute
63
+ # { "bicep" => 60 }
64
+ def from_xml( xml )
65
+ ToHashParser.from_xml(xml)
66
+ end
69
67
  end
70
68
 
71
69
  # convert this hash into a Mash for string or symbol key access
72
70
  def to_mash
73
- hash = Mash.new(self)
74
- hash.default = self.default
75
- hash
76
- end
77
-
78
- # convert this hash to a query string param
79
- # {:name => "Bob", :address => {:street => '111 Ruby Ave.', :city => 'Ruby Central', :phones => ['111-111-1111', '222-222-2222']}}
71
+ hash = Mash.new(self)
72
+ hash.default = default
73
+ hash
74
+ end
75
+
76
+ # Convert this hash to a query string:
77
+ #
78
+ # { :name => "Bob",
79
+ # :address => {
80
+ # :street => '111 Ruby Ave.',
81
+ # :city => 'Ruby Central',
82
+ # :phones => ['111-111-1111', '222-222-2222']
83
+ # }
84
+ # }.to_params
80
85
  # #=> "name=Bob&address[city]=Ruby Central&address[phones]=111-111-1111222-222-2222&address[street]=111 Ruby Ave."
86
+ #
81
87
  def to_params
82
- result = ''
88
+ params = ''
83
89
  stack = []
84
-
85
- each do |key, value|
86
- Hash === value ? stack << [key, value] : result << "#{key}=#{value}&"
90
+
91
+ each do |k, v|
92
+ if v.is_a?(Hash)
93
+ stack << [k,v]
94
+ else
95
+ params << "#{k}=#{v}&"
96
+ end
87
97
  end
88
-
98
+
89
99
  stack.each do |parent, hash|
90
- hash.each do |key, value|
91
- if Hash === value
92
- stack << ["#{parent}[#{key}]", value]
100
+ hash.each do |k, v|
101
+ if v.is_a?(Hash)
102
+ stack << ["#{parent}[#{k}]", v]
93
103
  else
94
- result << "#{parent}[#{key}]=#{value}&"
104
+ params << "#{parent}[#{k}]=#{v}&"
95
105
  end
96
106
  end
97
107
  end
98
- result.chop
108
+
109
+ params.chop! # trailing &
110
+ params
99
111
  end
100
112
 
101
- # lets through the keys in the argument
102
- # $ {:one => 1, :two => 2, :three => 3}.pass(:one)
103
- # #=> {:one=>1}
104
- def pass(*allowed)
105
- self.reject { |k,v| ! allowed.include?(k) }
113
+ # Returns a new Hash with only the selected keys:
114
+ #
115
+ # { :one => 1, :two => 2, :three => 3 }.only(:one)
116
+ # #=> { :one => 1 }
117
+ def only(*allowed)
118
+ reject { |k,v| !allowed.include?(k) }
106
119
  end
107
- alias only pass
108
120
 
109
- # blocks the keys in the arguments
110
- # $ {:one => 1, :two => 2, :three => 3}.block(:one)
111
- # #=> {:two=>2, :three=>3}
112
- def block(*rejected)
113
- self.reject { |k,v| rejected.include?(k) }
121
+ # Returns a new hash without the selected keys:
122
+ #
123
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
124
+ # #=> { :two => 2, :three => 3 }
125
+ #
126
+ def except(*rejected)
127
+ reject { |k,v| rejected.include?(k) }
114
128
  end
115
- alias except block
116
-
129
+
117
130
  # Converts the hash into xml attributes
131
+ #
118
132
  # { :one => "ONE", "two"=>"TWO" }.to_xml_attributes
119
133
  # #=> 'one="ONE" two="TWO"'
134
+ #
120
135
  def to_xml_attributes
121
136
  map do |k,v|
122
- "#{k.to_s.camelize.sub(/^(.{1,1})/){|m| m.downcase}}=\"#{v}\""
123
- end.join(" ")
137
+ %{#{k.to_s.camelize.sub(/^(.{1,1})/) { |m| m.downcase }}="#{v}"}
138
+ end.join(' ')
124
139
  end
125
140
 
126
141
  alias_method :to_html_attributes, :to_xml_attributes
127
-
142
+
128
143
  # Adds the given class symbol or string to the hash in the
129
144
  # :class key. This will add a html class if there are already any existing
130
145
  # or create the key and add this as the first class
131
- #
132
- # Example
146
+ #
133
147
  # @hash[:class] #=> nil
134
148
  # @hash.add_html_class!(:selected) #=> @hash[:class] == "selected"
135
- #
149
+ #
136
150
  # @hash.add_html_class!("class1 class2") #=> @hash[:class] == "selected class1 class2"
151
+ #
137
152
  def add_html_class!(html_class)
138
153
  if self[:class]
139
154
  self[:class] = "#{self[:class]} #{html_class}"
@@ -142,40 +157,38 @@ class Hash
142
157
  end
143
158
  end
144
159
 
145
- # Destructively convert all keys to symbols recursively.
160
+ # Destructively convert all keys which respond_to?(:to_sym) to symbols.
161
+ # Works recursively if given nested hashes.
162
+ #
163
+ # { 'one' => 1, 'two' => 2 }.symbolize_keys!
164
+ # #=> { :one => 1, :two => 2 }
165
+ #
146
166
  def symbolize_keys!
147
- keys.each do |key|
148
- unless key.is_a?(Symbol)
149
- self[key.to_sym] = self[key]
150
- delete(key)
151
- end
152
- if Hash === (sub = self[key.to_sym])
153
- sub.symbolize_keys!
154
- end
167
+ each do |k,v|
168
+ sym = k.respond_to?(:to_sym) ? k.to_sym : k
169
+ self[sym] = Hash === v ? v.symbolize_keys! : v
170
+ delete(k) unless k == sym
155
171
  end
156
172
  self
157
173
  end
158
174
 
159
- # Converts every key to an uppercase string (non-recursive.)
160
- # {:name => "Bob", "age" => 12, "nick" => "Bobinator"}.environmentize_keys!
161
- # #=> {"NAME"=>"Bob", "NICK"=>"Bobinator", "AGE"=>12}
175
+ # Destructively and non-recursively convert each key to an uppercase string.
176
+ #
177
+ # { :name => "Bob", "age" => 12, "nick" => "Bobinator" }.environmentize_keys!
178
+ # #=> { "NAME" => "Bob", "NICK" => "Bobinator", "AGE" => 12 }
179
+ #
162
180
  def environmentize_keys!
163
- self.each do |key, value|
181
+ keys.each do |key|
164
182
  self[key.to_s.upcase] = delete(key)
165
183
  end
166
184
  self
167
185
  end
168
-
169
- def method_missing(m,*a) #:nodoc:
170
- m.to_s =~ /=$/ ? self[$`]=a[0] : a==[] ? self[m] : raise(NoMethodError,"#{m}")
171
- end
172
186
 
173
- def respond_to?(method, include_private=false)
187
+ def respond_to?(method, include_private = false)
174
188
  return true if keys.include?(method)
175
189
  super(method, include_private)
176
190
  end
177
-
178
- end
191
+ end
179
192
 
180
193
  require 'rexml/parsers/streamparser'
181
194
  require 'rexml/parsers/baseparser'
@@ -208,29 +221,25 @@ class REXMLUtilityNode # :nodoc:
208
221
  #change repeating groups into an array
209
222
  # group by the first key of each element of the array to find repeating groups
210
223
  groups = @children.group_by{ |c| c.name }
211
-
212
- hash = {}
224
+
225
+ hash = {}
213
226
  groups.each do |key, values|
214
227
  if values.size == 1
215
- hash.merge!( values.first )
228
+ hash.merge! values.first
216
229
  else
217
- hash.merge!( key => values.map{ |element| element.to_hash[key] } )
230
+ hash.merge! key => values.map { |element| element.to_hash[key] }
218
231
  end
219
232
  end
220
-
233
+
221
234
  # merge the arrays, including attributes
222
- hash.merge!( attributes ) unless attributes.empty?
223
- return { name => hash }
235
+ hash.merge! attributes unless attributes.empty?
236
+
237
+ { name => hash }
224
238
  end
225
239
  end
226
-
227
- def to_s
228
- self.to_html
229
- end
230
-
231
-
240
+
232
241
  def typecast_value(value)
233
- return value unless attributes["type"]
242
+ return value unless attributes["type"]
234
243
 
235
244
  case attributes["type"]
236
245
  when "integer" then value.to_i
@@ -240,7 +249,7 @@ class REXMLUtilityNode # :nodoc:
240
249
  else value
241
250
  end
242
251
  end
243
-
252
+
244
253
  def translate_xml_entities(value)
245
254
  value.gsub(/&lt;/, "<").
246
255
  gsub(/&gt;/, ">").
@@ -248,46 +257,50 @@ class REXMLUtilityNode # :nodoc:
248
257
  gsub(/&apos;/, "'").
249
258
  gsub(/&amp;/, "&")
250
259
  end
251
-
252
- def undasherize_keys(params)
253
- params.keys.each do |key, vvalue|
254
- params[key.tr("-", "_")] = params.delete(key)
255
- end
256
- params
260
+
261
+ def undasherize_keys(params)
262
+ params.keys.each do |key, vvalue|
263
+ params[key.tr("-", "_")] = params.delete(key)
264
+ end
265
+ params
257
266
  end
258
-
267
+
259
268
  def inner_html
260
269
  @children.join
261
270
  end
262
-
263
- def to_html
271
+
272
+ def to_html
264
273
  "<#{name}#{attributes.to_xml_attributes}>#{inner_html}</#{name}>"
265
274
  end
275
+
276
+ def to_s
277
+ to_html
278
+ end
266
279
  end
267
280
 
268
281
  class ToHashParser # :nodoc:
269
- def self.from_xml(xml)
270
- stack = []
271
- parser = REXML::Parsers::BaseParser.new(xml)
272
-
273
- while true
274
- event = parser.pull
275
- case event[0]
276
- when :end_document
277
- break
278
- when :end_doctype, :start_doctype
279
- # do nothing
280
- when :start_element
281
- stack.push REXMLUtilityNode.new(event[1], event[2])
282
- when :end_element
283
- if stack.size > 1
284
- temp = stack.pop
285
- stack.last.add_node(temp)
286
- end
287
- when :text
288
- stack.last.add_node(event[1]) unless event[1].strip.length == 0
289
- end
290
- end
291
- stack.pop.to_hash
292
- end
282
+ def self.from_xml(xml)
283
+ stack = []
284
+ parser = REXML::Parsers::BaseParser.new(xml)
285
+
286
+ while true
287
+ event = parser.pull
288
+ case event[0]
289
+ when :end_document
290
+ break
291
+ when :end_doctype, :start_doctype
292
+ # do nothing
293
+ when :start_element
294
+ stack.push REXMLUtilityNode.new(event[1], event[2])
295
+ when :end_element
296
+ if stack.size > 1
297
+ temp = stack.pop
298
+ stack.last.add_node(temp)
299
+ end
300
+ when :text, :cdata
301
+ stack.last.add_node(event[1]) unless event[1].strip.length == 0
302
+ end
303
+ end
304
+ stack.pop.to_hash
305
+ end
293
306
  end