merb 0.4.2 → 0.5.0

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 (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