ratom-ssl 0.0.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 (49) hide show
  1. data/.gitignore +4 -0
  2. data/History.txt +118 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +295 -0
  5. data/Rakefile +55 -0
  6. data/VERSION.yml +5 -0
  7. data/lib/atom/configuration.rb +24 -0
  8. data/lib/atom/pub.rb +253 -0
  9. data/lib/atom/version.rb +7 -0
  10. data/lib/atom/xml/parser.rb +376 -0
  11. data/lib/atom.rb +771 -0
  12. data/ratom-ssl.gemspec +98 -0
  13. data/spec/app/member_entry.atom +31 -0
  14. data/spec/app/service.xml +36 -0
  15. data/spec/atom/pub_spec.rb +504 -0
  16. data/spec/atom_spec.rb +1336 -0
  17. data/spec/conformance/baseuri.atom +19 -0
  18. data/spec/conformance/divtest.atom +32 -0
  19. data/spec/conformance/linktests.xml +103 -0
  20. data/spec/conformance/nondefaultnamespace-baseline.atom +25 -0
  21. data/spec/conformance/nondefaultnamespace-xhtml.atom +25 -0
  22. data/spec/conformance/nondefaultnamespace.atom +25 -0
  23. data/spec/conformance/ordertest.xml +112 -0
  24. data/spec/conformance/title/html-cdata.atom +22 -0
  25. data/spec/conformance/title/html-entity.atom +22 -0
  26. data/spec/conformance/title/html-ncr.atom +22 -0
  27. data/spec/conformance/title/text-cdata.atom +22 -0
  28. data/spec/conformance/title/text-entity.atom +21 -0
  29. data/spec/conformance/title/text-ncr.atom +21 -0
  30. data/spec/conformance/title/xhtml-entity.atom +21 -0
  31. data/spec/conformance/title/xhtml-ncr.atom +21 -0
  32. data/spec/conformance/unknown-namespace.atom +25 -0
  33. data/spec/conformance/xmlbase.atom +133 -0
  34. data/spec/fixtures/complex_single_entry.atom +45 -0
  35. data/spec/fixtures/created_entry.atom +31 -0
  36. data/spec/fixtures/entry.atom +30 -0
  37. data/spec/fixtures/entry_with_custom_extensions.atom +7 -0
  38. data/spec/fixtures/entry_with_simple_extensions.atom +30 -0
  39. data/spec/fixtures/entry_with_single_custom_extension.atom +6 -0
  40. data/spec/fixtures/multiple_entry.atom +0 -0
  41. data/spec/fixtures/simple_single_entry.atom +21 -0
  42. data/spec/fixtures/with_stylesheet.atom +8 -0
  43. data/spec/paging/first_paged_feed.atom +21 -0
  44. data/spec/paging/last_paged_feed.atom +21 -0
  45. data/spec/paging/middle_paged_feed.atom +22 -0
  46. data/spec/property.rb +31 -0
  47. data/spec/spec.opts +1 -0
  48. data/spec/spec_helper.rb +43 -0
  49. metadata +140 -0
data/lib/atom/pub.rb ADDED
@@ -0,0 +1,253 @@
1
+ # Copyright (c) 2008 The Kaphan Foundation
2
+ #
3
+ # For licensing information see LICENSE.txt.
4
+ #
5
+ # Please visit http://www.peerworks.org/contact for further information.
6
+ #
7
+
8
+ require 'atom'
9
+ require 'atom/configuration'
10
+ require 'atom/xml/parser'
11
+ require 'atom/version'
12
+ require 'xml/libxml'
13
+ require 'uri'
14
+ require 'net/http'
15
+ require 'net/https'
16
+
17
+ module Atom
18
+ module Pub
19
+ class NotSupported < StandardError; end
20
+ class ProtocolError < StandardError
21
+ attr_reader :response
22
+ def initialize(response)
23
+ @response = response
24
+ end
25
+
26
+ def to_s
27
+ "Invalid response: #{@response}"
28
+ end
29
+ end
30
+
31
+ class Service
32
+ include Atom::Xml::Parseable
33
+ namespace Atom::Pub::NAMESPACE
34
+ elements :workspaces
35
+ loadable! do |reader, message, severity, base, line|
36
+ if severity == XML::Reader::SEVERITY_ERROR
37
+ raise ArgumentError, "#{message} at #{line}"
38
+ end
39
+ end
40
+
41
+ def initialize(xml = nil)
42
+ @workspaces = []
43
+
44
+ if xml
45
+ begin
46
+ if next_node_is?(xml, 'service', Atom::Pub::NAMESPACE)
47
+ xml.read
48
+ parse(xml)
49
+ else
50
+ raise ArgumentError, "XML document was missing atom:service"
51
+ end
52
+ ensure
53
+ xml.close
54
+ end
55
+ end
56
+
57
+ yield(self) if block_given?
58
+ end
59
+ end
60
+
61
+ class Categories < DelegateClass(Array)
62
+ include Atom::Xml::Parseable
63
+ elements :categories, :class => Atom::Category
64
+ attribute :href, :fixed
65
+
66
+ def initialize(o)
67
+ super([])
68
+ parse(o, :once => true)
69
+ o.read
70
+ parse(o)
71
+ end
72
+
73
+ remove_method :categories
74
+ def categories; self; end
75
+
76
+ # True true if fixed was 'yes' or 'true'
77
+ def fixed?
78
+ !self.fixed.nil? && %w(yes true).include?(self.fixed.downcase)
79
+ end
80
+ end
81
+
82
+ class Workspace
83
+ include Atom::Xml::Parseable
84
+ element :title, :class => Content, :namespace => Atom::NAMESPACE
85
+ elements :collections
86
+
87
+ def initialize(o = nil)
88
+ @collections = []
89
+
90
+ case o
91
+ when XML::Reader
92
+ o.read
93
+ parse(o)
94
+ when Hash
95
+ o.each do |k, v|
96
+ self.send("#{k}=".to_sym, v)
97
+ end
98
+ end
99
+
100
+ yield(self) if block_given?
101
+ end
102
+ end
103
+
104
+ class Collection
105
+ include Atom::Xml::Parseable
106
+ attribute :href
107
+ element :title, :class => Content, :namespace => Atom::NAMESPACE
108
+ element :categories, :class => Categories
109
+ elements :accepts, :content_only => true
110
+
111
+ def initialize(o = nil)
112
+ @accepts = []
113
+ case o
114
+ when XML::Reader
115
+ # do it once to get the attributes
116
+ parse(o, :once => true)
117
+ # now step into the element and the sub tree
118
+ o.read
119
+ parse(o)
120
+ when Hash
121
+ o.each do |k, v|
122
+ self.send("#{k}=", v)
123
+ end
124
+ end
125
+
126
+ yield(self) if block_given?
127
+ end
128
+
129
+ def feed(opts = {})
130
+ if href
131
+ Atom::Feed.load_feed(URI.parse(href), opts)
132
+ end
133
+ end
134
+
135
+ def publish(entry, opts = {})
136
+ uri = URI.parse(href)
137
+ response = nil
138
+ http_obj = Net::HTTP.new(uri.host, uri.port)
139
+ http_obj.use_ssl = true if uri.scheme == 'https'
140
+ http_obj.start do |http|
141
+ request = Net::HTTP::Post.new(uri.request_uri, headers)
142
+ if opts[:user] && opts[:pass]
143
+ request.basic_auth(opts[:user], opts[:pass])
144
+ elsif opts[:hmac_access_id] && opts[:hmac_secret_key]
145
+ if Atom::Configuration.auth_hmac_enabled?
146
+ AuthHMAC.sign!(request, opts[:hmac_access_id], opts[:hmac_secret_key])
147
+ else
148
+ raise ArgumentError, "AuthHMAC credentials provides by auth-hmac gem is not installed"
149
+ end
150
+ end
151
+ response = http.request(request, entry.to_xml.to_s)
152
+ end
153
+
154
+ case response
155
+ when Net::HTTPCreated
156
+ published = begin
157
+ Atom::Entry.load_entry(response.body)
158
+ rescue ArgumentError
159
+ entry
160
+ end
161
+
162
+ if response['Location']
163
+ if published.edit_link
164
+ published.edit_link.href = response['Location']
165
+ else
166
+ published.links << Atom::Link.new(:rel => 'edit', :href => response['Location'])
167
+ end
168
+ end
169
+
170
+ published
171
+ else
172
+ raise Atom::Pub::ProtocolError, response
173
+ end
174
+ end
175
+
176
+ private
177
+ def headers
178
+ {'Accept' => 'application/atom+xml',
179
+ 'Content-Type' => 'application/atom+xml;type=entry',
180
+ 'User-Agent' => "rAtom #{Atom::VERSION::STRING}"
181
+ }
182
+ end
183
+ end
184
+ end
185
+
186
+ class Entry
187
+ def save!(opts = {})
188
+ if edit = edit_link
189
+ uri = URI.parse(edit.href)
190
+ response = nil
191
+ Net::HTTP.start(uri.host, uri.port) do |http|
192
+ request = Net::HTTP::Put.new(uri.request_uri, headers)
193
+ if opts[:user] && opts[:pass]
194
+ request.basic_auth(opts[:user], opts[:pass])
195
+ elsif opts[:hmac_access_id] && opts[:hmac_secret_key]
196
+ if Atom::Configuration.auth_hmac_enabled?
197
+ AuthHMAC.sign!(request, opts[:hmac_access_id], opts[:hmac_secret_key])
198
+ else
199
+ raise ArgumentError, "AuthHMAC credentials provides by auth-hmac gem is not installed"
200
+ end
201
+ end
202
+
203
+ response = http.request(request, self.to_xml)
204
+ end
205
+
206
+ case response
207
+ when Net::HTTPSuccess
208
+ else
209
+ raise Atom::Pub::ProtocolError, response
210
+ end
211
+ else
212
+ raise Atom::Pub::NotSupported, "Entry does not have an edit link"
213
+ end
214
+ end
215
+
216
+ def destroy!(opts = {})
217
+ if edit = edit_link
218
+ uri = URI.parse(edit.href)
219
+ response = nil
220
+ Net::HTTP.start(uri.host, uri.port) do |http|
221
+ request = Net::HTTP::Delete.new(uri.request_uri, {'Accept' => 'application/atom+xml', 'User-Agent' => "rAtom #{Atom::VERSION::STRING}"})
222
+ if opts[:user] && opts[:pass]
223
+ request.basic_auth(opts[:user], opts[:pass])
224
+ elsif opts[:hmac_access_id] && opts[:hmac_secret_key]
225
+ if Atom::Configuration.auth_hmac_enabled?
226
+ AuthHMAC.sign!(request, opts[:hmac_access_id], opts[:hmac_secret_key])
227
+ else
228
+ raise ArgumentError, "AuthHMAC credentials provides by auth-hmac gem is not installed"
229
+ end
230
+ end
231
+
232
+ response = http.request(request)
233
+ end
234
+
235
+ case response
236
+ when Net::HTTPSuccess
237
+ else
238
+ raise Atom::Pub::ProtocolError, response
239
+ end
240
+ else
241
+ raise Atom::Pub::NotSupported, "Entry does not have an edit link"
242
+ end
243
+ end
244
+
245
+ private
246
+ def headers
247
+ {'Accept' => 'application/atom+xml',
248
+ 'Content-Type' => 'application/atom+xml;type=entry',
249
+ 'User-Agent' => "rAtom #{Atom::VERSION::STRING}"
250
+ }
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,7 @@
1
+ require 'yaml'
2
+ module Atom
3
+ module VERSION
4
+ INFO = YAML.load_file(File.join(File.dirname(__FILE__), "..", "..", "VERSION.yml"))
5
+ STRING = [:major, :minor, :patch, :build].map {|l| INFO[l]}.compact.join('.')
6
+ end
7
+ end
@@ -0,0 +1,376 @@
1
+ # Copyright (c) 2008 The Kaphan Foundation
2
+ #
3
+ # For licensing information see LICENSE.
4
+ #
5
+ # Please visit http://www.peerworks.org/contact for further information.
6
+ #
7
+ require 'net/http'
8
+ require 'net/https'
9
+ require 'time'
10
+
11
+ # Just a couple methods form transforming strings
12
+ unless defined?(ActiveSupport)
13
+ class String # :nodoc:
14
+ def singularize
15
+ if self =~ /ies$/
16
+ self.sub(/ies$/, 'y')
17
+ else
18
+ self.sub(/s$/, '')
19
+ end
20
+ end
21
+
22
+ def demodulize
23
+ self.sub(/.*::/, '')
24
+ end
25
+
26
+ def constantize
27
+ Object.module_eval("::#{self}", __FILE__, __LINE__)
28
+ end
29
+ end
30
+ end
31
+
32
+ module Atom
33
+ class LoadError < StandardError
34
+ attr_reader :response
35
+ def initialize(response)
36
+ @response = response
37
+ end
38
+
39
+ def to_s
40
+ "Atom::LoadError: #{response.code} #{response.message}"
41
+ end
42
+ end
43
+
44
+ module Xml # :nodoc:
45
+ class NamespaceMap
46
+ def initialize(default = Atom::NAMESPACE)
47
+ @default = default
48
+ @i = 0
49
+ @map = {}
50
+ end
51
+
52
+ def prefix(ns, element)
53
+ if ns == @default
54
+ element
55
+ else
56
+ "#{get(ns)}:#{element}"
57
+ end
58
+ end
59
+
60
+ def get(ns)
61
+ if ns == Atom::NAMESPACE
62
+ @map[ns] = "atom"
63
+ elsif ns == Atom::Pub::NAMESPACE
64
+ @map[ns] = "app"
65
+ else
66
+ @map[ns] or @map[ns] = "ns#{@i += 1}"
67
+ end
68
+ end
69
+
70
+ def each(&block)
71
+ @map.each(&block)
72
+ end
73
+ end
74
+
75
+ module Parseable # :nodoc:
76
+ def parse(xml, options = {})
77
+ starting_depth = xml.depth
78
+ loop do
79
+ case xml.node_type
80
+ when XML::Reader::TYPE_ELEMENT
81
+ if element_specs.include?(xml.local_name) && (self.class.known_namespaces + [Atom::NAMESPACE, Atom::Pub::NAMESPACE]).include?(xml.namespace_uri)
82
+ element_specs[xml.local_name].parse(self, xml)
83
+ elsif attributes.any?
84
+ while (xml.move_to_next_attribute == 1)
85
+ if attributes.include?(xml.name)
86
+ # Support attribute names with namespace prefixes
87
+ self.send("#{xml.name.sub(/:/, '_')}=", xml.value)
88
+ elsif self.respond_to?(:simple_extensions)
89
+ self[xml.namespace_uri, xml.local_name].as_attribute = true
90
+ self[xml.namespace_uri, xml.local_name] << xml.value
91
+ end
92
+ end
93
+ elsif self.respond_to?(:simple_extensions)
94
+ self[xml.namespace_uri, xml.local_name] << xml.read_inner_xml
95
+ end
96
+ end
97
+ break unless !options[:once] && xml.next == 1 && xml.depth >= starting_depth
98
+ end
99
+ end
100
+
101
+ def next_node_is?(xml, element, ns = nil)
102
+ # Get to the next element
103
+ while xml.next == 1 && xml.node_type != XML::Reader::TYPE_ELEMENT; end
104
+ current_node_is?(xml, element, ns)
105
+ end
106
+
107
+ def current_node_is?(xml, element, ns = nil)
108
+ xml.node_type == XML::Reader::TYPE_ELEMENT && xml.local_name == element && (ns.nil? || ns == xml.namespace_uri)
109
+ end
110
+
111
+ def Parseable.included(o)
112
+ o.class_eval do
113
+ def o.ordered_element_specs; @ordered_element_specs ||= []; end
114
+ def o.element_specs; @element_specs ||= {}; end
115
+ def o.attributes; @attributes ||= []; end
116
+ def element_specs; self.class.element_specs; end
117
+ def ordered_element_specs; self.class.ordered_element_specs; end
118
+ def attributes; self.class.attributes; end
119
+ def o.namespace(ns = @namespace); @namespace = ns; end
120
+ def o.add_extension_namespace(ns, url); self.extensions_namespaces[ns.to_s] = url; end
121
+ def o.extensions_namespaces; @extensions_namespaces ||= {} end
122
+ def o.known_namespaces; @known_namespaces ||= [] end
123
+ end
124
+ o.send(:extend, DeclarationMethods)
125
+ end
126
+
127
+ def ==(o)
128
+ if self.object_id == o.object_id
129
+ true
130
+ elsif o.instance_of?(self.class)
131
+ self.class.element_specs.values.all? do |spec|
132
+ self.send(spec.attribute) == o.send(spec.attribute)
133
+ end
134
+ else
135
+ false
136
+ end
137
+ end
138
+
139
+ # There doesn't seem to be a way to set namespaces using libxml-ruby,
140
+ # so ratom has to manage namespace to URI prefixing itself, which
141
+ # makes this method more complicated that it needs to be.
142
+ #
143
+ def to_xml(nodeonly = false, root_name = self.class.name.demodulize.downcase, namespace = nil, namespace_map = nil)
144
+ namespace_map = NamespaceMap.new(self.class.namespace) if namespace_map.nil?
145
+ node = XML::Node.new(root_name)
146
+ node['xmlns'] = self.class.namespace unless nodeonly || !self.class.respond_to?(:namespace)
147
+ self.class.extensions_namespaces.each do |ns_alias,uri|
148
+ node["xmlns:#{ns_alias}"] = uri
149
+ end
150
+
151
+ self.class.ordered_element_specs.each do |spec|
152
+ if spec.single?
153
+ if attribute = self.send(spec.attribute)
154
+ if attribute.respond_to?(:to_xml)
155
+ node << attribute.to_xml(true, spec.name, spec.options[:namespace], namespace_map)
156
+ else
157
+ n = XML::Node.new(spec.name)
158
+ n['xmlns'] = spec.options[:namespace] if spec.options[:namespace]
159
+ n << (attribute.is_a?(Time)? attribute.xmlschema : attribute.to_s)
160
+ node << n
161
+ end
162
+ end
163
+ else
164
+ self.send(spec.attribute).each do |attribute|
165
+ if attribute.respond_to?(:to_xml)
166
+ node << attribute.to_xml(true, spec.name.singularize, nil, namespace_map)
167
+ else
168
+ n = XML::Node.new(spec.name.singularize)
169
+ n['xmlns'] = spec.options[:namespace] if spec.options[:namespace]
170
+ n << attribute.to_s
171
+ node << n
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ self.class.attributes.each do |attribute|
178
+ if value = self.send("#{attribute.sub(/:/, '_')}")
179
+ if value != 0
180
+ node[attribute] = value.to_s
181
+ end
182
+ end
183
+ end
184
+
185
+ if self.respond_to?(:simple_extensions) && self.simple_extensions
186
+ self.simple_extensions.each do |name, value_array|
187
+ if name =~ /\{(.*),(.*)\}/
188
+ value_array.each do |value|
189
+ if value_array.as_attribute
190
+ node["#{namespace_map.get($1)}:#{$2}"] = value
191
+ else
192
+ ext = XML::Node.new("#{namespace_map.get($1)}:#{$2}")
193
+ ext << value
194
+ node << ext
195
+ end
196
+ end
197
+ else
198
+ STDERR.print "Couldn't split #{name}"
199
+ end
200
+ end
201
+ end
202
+
203
+ unless nodeonly
204
+ namespace_map.each do |ns, prefix|
205
+ node["xmlns:#{prefix}"] = ns
206
+ end
207
+
208
+ doc = XML::Document.new
209
+ doc.root = node
210
+ doc.to_s
211
+ else
212
+ node
213
+ end
214
+ end
215
+
216
+ module DeclarationMethods # :nodoc:
217
+ def element(*names)
218
+ options = {:type => :single}
219
+ options.merge!(names.pop) if names.last.is_a?(Hash)
220
+
221
+ names.each do |name|
222
+ attr_accessor name.to_s.sub(/:/, '_').to_sym
223
+ ns, local_name = name.to_s[/(.*):(.*)/,1], $2 || name
224
+ self.known_namespaces << self.extensions_namespaces[ns] if ns
225
+ self.ordered_element_specs << self.element_specs[local_name.to_s] = ParseSpec.new(name, options)
226
+ end
227
+ end
228
+
229
+ def elements(*names)
230
+ options = {:type => :collection}
231
+ options.merge!(names.pop) if names.last.is_a?(Hash)
232
+
233
+ names.each do |name|
234
+ name_sym = name.to_s.sub(/:/, '_').to_sym
235
+ attr_writer name_sym
236
+ define_method name_sym do
237
+ ivar = :"@#{name_sym}"
238
+ self.instance_variable_set ivar, [] unless self.instance_variable_defined? ivar
239
+ self.instance_variable_get ivar
240
+ end
241
+ ns, local_name = name.to_s[/(.*):(.*)/,1], $2 || name
242
+ self.known_namespaces << self.extensions_namespaces[ns] if ns
243
+ self.ordered_element_specs << self.element_specs[local_name.to_s.singularize] = ParseSpec.new(name, options)
244
+ end
245
+ end
246
+
247
+ def attribute(*names)
248
+ names.each do |name|
249
+ attr_accessor name.to_s.sub(/:/, '_').to_sym
250
+ self.attributes << name.to_s
251
+ end
252
+ end
253
+
254
+ def loadable!(&error_handler)
255
+ class_name = self.name
256
+ (class << self; self; end).instance_eval do
257
+
258
+ define_method "load_#{class_name.demodulize.downcase}" do |*args|
259
+ o = args.first
260
+ opts = args.size > 1 ? args.last : {}
261
+
262
+ xml =
263
+ case o
264
+ when String
265
+ XML::Reader.string(o)
266
+ when IO
267
+ XML::Reader.io(o)
268
+ when URI
269
+ # raise ArgumentError, "#{class_name}.load only handles http & https URIs" if o.scheme != 'https' || o.scheme != 'http'
270
+ response = nil
271
+ http_obj = Net::HTTP.new(o.host, o.port)
272
+ http_obj.use_ssl = true if o.scheme == 'https'
273
+ http_obj.start do |http|
274
+ request = Net::HTTP::Get.new(o.request_uri)
275
+ if opts[:user] && opts[:pass]
276
+ request.basic_auth(opts[:user], opts[:pass])
277
+ elsif opts[:hmac_access_id] && opts[:hmac_secret_key]
278
+ if Atom::Configuration.auth_hmac_enabled?
279
+ AuthHMAC.sign!(request, opts[:hmac_access_id], opts[:hmac_secret_key])
280
+ else
281
+ raise ArgumentError, "AuthHMAC credentials provides by auth-hmac gem is not installed"
282
+ end
283
+ end
284
+ response = http.request(request)
285
+ end
286
+ case response
287
+ when Net::HTTPSuccess
288
+ XML::Reader.string(response.body)
289
+ when nil
290
+ raise ArgumentError.new("nil response to #{o}")
291
+ else
292
+ raise Atom::LoadError.new(response)
293
+ end
294
+ else
295
+ raise ArgumentError, "#{class_name}.load needs String, URI or IO, got #{o.class.name}"
296
+ end
297
+
298
+ if error_handler
299
+ XML::Error.set_handler(&error_handler)
300
+ else
301
+ XML::Error.set_handler do |reader, message, severity, base, line|
302
+ if severity == XML::Reader::SEVERITY_ERROR
303
+ raise ArgumentError, "#{message} at #{line} in #{o}"
304
+ end
305
+ end
306
+ end
307
+
308
+ o = self.new(xml)
309
+ xml.close
310
+ o
311
+ end
312
+ end
313
+ end
314
+
315
+ def parse(xml)
316
+ new(xml)
317
+ end
318
+ end
319
+
320
+ # Contains the specification for how an element should be parsed.
321
+ #
322
+ # This should not need to be constructed directly, instead use the
323
+ # element and elements macros in the declaration of the class.
324
+ #
325
+ # See Parseable.
326
+ #
327
+ class ParseSpec # :nodoc:
328
+ attr_reader :name, :options, :attribute
329
+
330
+ def initialize(name, options = {})
331
+ @name = name.to_s
332
+ @attribute = name.to_s.sub(/:/, '_')
333
+ @options = options
334
+ end
335
+
336
+ # Parses a chunk of XML according the specification.
337
+ # The data extracted will be assigned to the target object.
338
+ #
339
+ def parse(target, xml)
340
+ case options[:type]
341
+ when :single
342
+ target.send("#{@attribute}=".to_sym, build(target, xml))
343
+ when :collection
344
+ collection = target.send(@attribute.to_s)
345
+ element = build(target, xml)
346
+ collection << element
347
+ end
348
+ end
349
+
350
+ def single?
351
+ options[:type] == :single
352
+ end
353
+
354
+ private
355
+ # Create a member
356
+ def build(target, xml)
357
+ if options[:class].is_a?(Class)
358
+ if options[:content_only]
359
+ options[:class].parse(xml.read_string)
360
+ else
361
+ options[:class].parse(xml)
362
+ end
363
+ elsif options[:type] == :single
364
+ xml.read_string
365
+ elsif options[:content_only]
366
+ xml.read_string
367
+ else
368
+ target_class = target.class.name
369
+ target_class = target_class.sub(/#{target_class.demodulize}$/, name.singularize.capitalize)
370
+ target_class.constantize.parse(xml)
371
+ end
372
+ end
373
+ end
374
+ end
375
+ end
376
+ end