ratom-instructure 0.6.9

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