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.
- data/.gitignore +4 -0
- data/History.txt +118 -0
- data/LICENSE +20 -0
- data/README.rdoc +295 -0
- data/Rakefile +55 -0
- data/VERSION.yml +5 -0
- data/lib/atom/configuration.rb +24 -0
- data/lib/atom/pub.rb +253 -0
- data/lib/atom/version.rb +7 -0
- data/lib/atom/xml/parser.rb +376 -0
- data/lib/atom.rb +771 -0
- data/ratom-ssl.gemspec +98 -0
- data/spec/app/member_entry.atom +31 -0
- data/spec/app/service.xml +36 -0
- data/spec/atom/pub_spec.rb +504 -0
- data/spec/atom_spec.rb +1336 -0
- data/spec/conformance/baseuri.atom +19 -0
- data/spec/conformance/divtest.atom +32 -0
- data/spec/conformance/linktests.xml +103 -0
- data/spec/conformance/nondefaultnamespace-baseline.atom +25 -0
- data/spec/conformance/nondefaultnamespace-xhtml.atom +25 -0
- data/spec/conformance/nondefaultnamespace.atom +25 -0
- data/spec/conformance/ordertest.xml +112 -0
- data/spec/conformance/title/html-cdata.atom +22 -0
- data/spec/conformance/title/html-entity.atom +22 -0
- data/spec/conformance/title/html-ncr.atom +22 -0
- data/spec/conformance/title/text-cdata.atom +22 -0
- data/spec/conformance/title/text-entity.atom +21 -0
- data/spec/conformance/title/text-ncr.atom +21 -0
- data/spec/conformance/title/xhtml-entity.atom +21 -0
- data/spec/conformance/title/xhtml-ncr.atom +21 -0
- data/spec/conformance/unknown-namespace.atom +25 -0
- data/spec/conformance/xmlbase.atom +133 -0
- data/spec/fixtures/complex_single_entry.atom +45 -0
- data/spec/fixtures/created_entry.atom +31 -0
- data/spec/fixtures/entry.atom +30 -0
- data/spec/fixtures/entry_with_custom_extensions.atom +7 -0
- data/spec/fixtures/entry_with_simple_extensions.atom +30 -0
- data/spec/fixtures/entry_with_single_custom_extension.atom +6 -0
- data/spec/fixtures/multiple_entry.atom +0 -0
- data/spec/fixtures/simple_single_entry.atom +21 -0
- data/spec/fixtures/with_stylesheet.atom +8 -0
- data/spec/paging/first_paged_feed.atom +21 -0
- data/spec/paging/last_paged_feed.atom +21 -0
- data/spec/paging/middle_paged_feed.atom +22 -0
- data/spec/property.rb +31 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +43 -0
- 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
|
data/lib/atom/version.rb
ADDED
@@ -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
|