ratom-instructure 0.6.9

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