merb 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +21 -14
- data/Rakefile +157 -108
- data/SVN_REVISION +1 -0
- data/app_generators/merb/templates/Rakefile +20 -4
- data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +1 -1
- data/app_generators/merb/templates/config/boot.rb +1 -1
- data/app_generators/merb/templates/config/dependencies.rb +3 -3
- data/app_generators/merb/templates/config/merb.yml +5 -0
- data/app_generators/merb/templates/config/merb_init.rb +3 -3
- data/app_generators/merb/templates/script/destroy +3 -0
- data/app_generators/merb/templates/script/generate +1 -1
- data/app_generators/merb/templates/spec/spec_helper.rb +2 -2
- data/app_generators/merb/templates/test/test_helper.rb +1 -1
- data/app_generators/merb_plugin/merb_plugin_generator.rb +4 -0
- data/bin/merb +1 -3
- data/lib/merb.rb +144 -76
- data/lib/merb/abstract_controller.rb +6 -5
- data/lib/merb/assets.rb +119 -0
- data/lib/merb/boot_loader.rb +217 -0
- data/lib/merb/caching.rb +1 -1
- data/lib/merb/caching/action_cache.rb +1 -1
- data/lib/merb/caching/fragment_cache.rb +1 -1
- data/lib/merb/caching/store/file_cache.rb +1 -1
- data/lib/merb/config.rb +290 -0
- data/lib/merb/controller.rb +5 -5
- data/lib/merb/core_ext/get_args.rb +1 -0
- data/lib/merb/core_ext/hash.rb +182 -169
- data/lib/merb/core_ext/kernel.rb +57 -26
- data/lib/merb/dispatcher.rb +6 -6
- data/lib/merb/drb_server.rb +1 -1
- data/lib/merb/generators/merb_generator_helpers.rb +7 -6
- data/lib/merb/logger.rb +1 -1
- data/lib/merb/mail_controller.rb +3 -4
- data/lib/merb/mailer.rb +2 -2
- data/lib/merb/mixins/basic_authentication.rb +2 -2
- data/lib/merb/mixins/controller.rb +1 -1
- data/lib/merb/mixins/general_controller.rb +13 -20
- data/lib/merb/mixins/inline_partial.rb +32 -0
- data/lib/merb/mixins/render.rb +3 -3
- data/lib/merb/mixins/responder.rb +1 -1
- data/lib/merb/mixins/view_context.rb +159 -33
- data/lib/merb/mongrel_handler.rb +9 -9
- data/lib/merb/plugins.rb +1 -1
- data/lib/merb/request.rb +25 -1
- data/lib/merb/router.rb +264 -226
- data/lib/merb/server.rb +66 -560
- data/lib/merb/session/cookie_store.rb +14 -13
- data/lib/merb/session/mem_cache_session.rb +20 -10
- data/lib/merb/session/memory_session.rb +21 -11
- data/lib/merb/template.rb +2 -2
- data/lib/merb/template/erubis.rb +3 -33
- data/lib/merb/template/haml.rb +8 -3
- data/lib/merb/test/fake_request.rb +8 -3
- data/lib/merb/test/helper.rb +66 -22
- data/lib/merb/test/rspec.rb +9 -155
- data/lib/merb/test/rspec_matchers/controller_matchers.rb +117 -0
- data/lib/merb/test/rspec_matchers/markup_matchers.rb +98 -0
- data/lib/merb/upload_handler.rb +2 -1
- data/lib/merb/version.rb +38 -3
- data/lib/merb/view_context.rb +1 -2
- data/lib/tasks/merb.rake +11 -11
- data/merb_generators/part_controller/USAGE +5 -0
- data/merb_generators/part_controller/part_controller_generator.rb +27 -0
- data/merb_generators/part_controller/templates/controller.rb +8 -0
- data/merb_generators/part_controller/templates/helper.rb +5 -0
- data/merb_generators/part_controller/templates/index.html.erb +3 -0
- data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +1 -1
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/controllers/dispatch_spec_controllers.rb +9 -1
- data/spec/fixtures/controllers/render_spec_controllers.rb +5 -5
- data/spec/fixtures/models/router_spec_models.rb +10 -0
- data/spec/merb/abstract_controller_spec.rb +2 -2
- data/spec/merb/assets_spec.rb +207 -0
- data/spec/merb/caching_spec.rb +2 -2
- data/spec/merb/controller_spec.rb +7 -2
- data/spec/merb/cookie_store_spec.rb +1 -1
- data/spec/merb/core_ext/class_spec.rb +97 -0
- data/spec/merb/core_ext/enumerable_spec.rb +27 -0
- data/spec/merb/core_ext/hash_spec.rb +251 -0
- data/spec/merb/core_ext/inflector_spec.rb +34 -0
- data/spec/merb/core_ext/kernel_spec.rb +25 -0
- data/spec/merb/core_ext/numeric_spec.rb +26 -0
- data/spec/merb/core_ext/object_spec.rb +47 -0
- data/spec/merb/core_ext/string_spec.rb +22 -0
- data/spec/merb/core_ext/symbol_spec.rb +7 -0
- data/spec/merb/dependency_spec.rb +22 -0
- data/spec/merb/dispatch_spec.rb +23 -12
- data/spec/merb/fake_request_spec.rb +8 -0
- data/spec/merb/generator_spec.rb +140 -21
- data/spec/merb/handler_spec.rb +5 -5
- data/spec/merb/mail_controller_spec.rb +3 -3
- data/spec/merb/render_spec.rb +1 -1
- data/spec/merb/responder_spec.rb +3 -3
- data/spec/merb/router_spec.rb +260 -191
- data/spec/merb/server_spec.rb +5 -5
- data/spec/merb/upload_handler_spec.rb +7 -0
- data/spec/merb/version_spec.rb +33 -0
- data/spec/merb/view_context_spec.rb +217 -59
- data/spec/spec_generator_helper.rb +15 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/spec_helpers/url_shared_behaviour.rb +5 -7
- metadata +32 -7
- data/lib/merb/caching/store/memcache.rb +0 -20
- data/lib/merb/mixins/form_control.rb +0 -332
- data/lib/patch +0 -69
- data/spec/merb/core_ext_spec.rb +0 -464
- data/spec/merb/form_control_mixin_spec.rb +0 -431
data/lib/merb/controller.rb
CHANGED
@@ -18,7 +18,7 @@ module Merb
|
|
18
18
|
#
|
19
19
|
# === Session Store
|
20
20
|
#
|
21
|
-
# The session store is set in
|
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::
|
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::
|
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
|
-
|
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.
|
data/lib/merb/core_ext/hash.rb
CHANGED
@@ -1,139 +1,154 @@
|
|
1
|
-
#require 'hpricot'
|
1
|
+
# require 'hpricot'
|
2
2
|
|
3
3
|
class Hash
|
4
|
-
|
5
4
|
class << self
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
88
|
+
params = ''
|
83
89
|
stack = []
|
84
|
-
|
85
|
-
each do |
|
86
|
-
|
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 |
|
91
|
-
if Hash
|
92
|
-
stack << ["#{parent}[#{
|
100
|
+
hash.each do |k, v|
|
101
|
+
if v.is_a?(Hash)
|
102
|
+
stack << ["#{parent}[#{k}]", v]
|
93
103
|
else
|
94
|
-
|
104
|
+
params << "#{parent}[#{k}]=#{v}&"
|
95
105
|
end
|
96
106
|
end
|
97
107
|
end
|
98
|
-
|
108
|
+
|
109
|
+
params.chop! # trailing &
|
110
|
+
params
|
99
111
|
end
|
100
112
|
|
101
|
-
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
|
105
|
-
|
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
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
|
113
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
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
|
-
#
|
160
|
-
#
|
161
|
-
#
|
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
|
-
|
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!
|
228
|
+
hash.merge! values.first
|
216
229
|
else
|
217
|
-
hash.merge!
|
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!
|
223
|
-
|
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(/</, "<").
|
246
255
|
gsub(/>/, ">").
|
@@ -248,46 +257,50 @@ class REXMLUtilityNode # :nodoc:
|
|
248
257
|
gsub(/'/, "'").
|
249
258
|
gsub(/&/, "&")
|
250
259
|
end
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|