atom-tools 0.9.0 → 2.0.5
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/COPYING +3 -3
- data/README +9 -49
- data/Rakefile +65 -62
- data/bin/atom-cp +159 -0
- data/bin/atom-grep +78 -0
- data/bin/atom-post +73 -0
- data/bin/atom-purge +82 -0
- data/doc/classes/Atom/AttrEl.html +163 -0
- data/doc/classes/Atom/Author.html +2 -19
- data/doc/classes/Atom/AutodiscoveryFailure.html +111 -0
- data/doc/classes/Atom/Categories.html +168 -0
- data/doc/classes/Atom/Category.html +10 -1
- data/doc/classes/Atom/Collection.html +194 -91
- data/doc/classes/Atom/Content.html +42 -1
- data/doc/classes/Atom/Contributor.html +2 -8
- data/doc/classes/Atom/Control.html +126 -0
- data/doc/classes/Atom/Converters.html +571 -0
- data/doc/classes/Atom/DigestAuth.html +285 -0
- data/doc/classes/Atom/Element.html +673 -106
- data/doc/classes/Atom/FileCache.html +287 -0
- data/doc/classes/Atom/HTTP.html +211 -95
- data/doc/classes/Atom/HTTPResponse.html +190 -0
- data/doc/classes/Atom/HasCategories.html +187 -0
- data/doc/classes/Atom/HasLinks.html +168 -0
- data/doc/classes/Atom/Link.html +80 -3
- data/doc/classes/Atom/NilCache.html +202 -0
- data/doc/classes/Atom/ParseError.html +111 -0
- data/doc/classes/Atom/Parsers.html +311 -0
- data/doc/classes/Atom/Person.html +130 -0
- data/doc/classes/Atom/Rights.html +113 -0
- data/doc/classes/Atom/Service.html +298 -0
- data/doc/{files/lib/atom/yaml_rb.html → classes/Atom/Source.html} +26 -24
- data/doc/classes/Atom/Subtitle.html +113 -0
- data/doc/classes/Atom/Summary.html +113 -0
- data/doc/classes/Atom/Text.html +196 -53
- data/doc/classes/Atom/Title.html +113 -0
- data/doc/classes/Atom/Tools.html +525 -0
- data/doc/classes/Atom/Workspace.html +121 -0
- data/doc/classes/Object.html +204 -0
- data/doc/created.rid +1 -1
- data/doc/files/README.html +9 -49
- data/doc/files/lib/atom/cache_rb.html +302 -0
- data/doc/files/lib/atom/collection_rb.html +1 -2
- data/doc/files/lib/atom/element_rb.html +2 -1
- data/doc/files/lib/atom/entry_rb.html +2 -2
- data/doc/files/lib/atom/feed_rb.html +1 -2
- data/doc/files/lib/atom/http_rb.html +6 -1
- data/doc/files/lib/atom/{xml_rb.html → service_rb.html} +8 -6
- data/doc/files/lib/atom/text_rb.html +2 -1
- data/doc/files/lib/atom/{app_rb.html → tools_rb.html} +4 -6
- data/doc/fr_class_index.html +23 -3
- data/doc/fr_file_index.html +3 -3
- data/doc/fr_method_index.html +111 -36
- data/lib/atom/cache.rb +178 -0
- data/lib/atom/collection.rb +90 -40
- data/lib/atom/element.rb +569 -161
- data/lib/atom/entry.rb +63 -89
- data/lib/atom/feed.rb +65 -67
- data/lib/atom/http.rb +421 -49
- data/lib/atom/service.rb +106 -0
- data/lib/atom/text.rb +138 -61
- data/lib/atom/tools.rb +163 -0
- data/spec/entry_spec.rb +364 -0
- data/spec/ext_spec.rb +42 -0
- data/spec/feed_spec.rb +39 -0
- data/spec/fixtures/contacts-feed.xml +34 -0
- data/spec/fixtures/entry-w-ext.xml +15 -0
- data/spec/fixtures/entry-w-xml.xml +24 -0
- data/spec/fixtures/entry.xml +42 -0
- data/spec/fixtures/feed-w-ext.xml +33 -0
- data/spec/fixtures/service-w-xhtml-ns.xml +21 -0
- data/spec/fixtures/service.xml +36 -0
- data/spec/service_spec.rb +108 -0
- data/spec/spec_helper.rb +9 -0
- data/test/conformance/order.rb +11 -10
- data/test/conformance/title.rb +9 -9
- data/test/conformance/updated.rb +2 -1
- data/test/runtests.rb +0 -0
- data/test/test_constructs.rb +78 -8
- data/test/test_feed.rb +35 -29
- data/test/test_general.rb +3 -30
- data/test/test_http.rb +248 -34
- data/test/test_protocol.rb +143 -24
- data/test/test_xml.rb +145 -53
- metadata +116 -62
- data/bin/atom-client.rb +0 -246
- data/bin/atom-server.rb~ +0 -71
- data/doc/classes/Atom/App.html +0 -217
- data/doc/classes/Atom/Entry.html +0 -365
- data/doc/classes/Atom/Feed.html +0 -585
- data/lib/atom/app.rb +0 -87
- data/lib/atom/xml.rb +0 -200
- data/lib/atom/yaml.rb +0 -101
data/lib/atom/element.rb
CHANGED
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
require "time"
|
|
2
2
|
require "rexml/element"
|
|
3
3
|
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
module URI # :nodoc: all
|
|
7
|
+
class Generic; def to_uri; self; end; end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class String # :nodoc:
|
|
11
|
+
def to_uri; URI.parse(self); end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# cribbed from metaid.rb
|
|
15
|
+
class Object
|
|
16
|
+
# The hidden singleton lurks behind everyone
|
|
17
|
+
def metaclass; class << self; self; end; end
|
|
18
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
|
19
|
+
|
|
20
|
+
# Adds methods to a metaclass
|
|
21
|
+
def meta_def name, &blk
|
|
22
|
+
meta_eval { define_method name, &blk }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
4
26
|
module Atom # :nodoc:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return if date.nil?
|
|
27
|
+
NS = "http://www.w3.org/2005/Atom"
|
|
28
|
+
PP_NS = "http://www.w3.org/2007/app"
|
|
8
29
|
|
|
9
|
-
|
|
10
|
-
date
|
|
11
|
-
else
|
|
12
|
-
Time.parse date.to_s
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def date.to_s
|
|
16
|
-
iso8601
|
|
17
|
-
end
|
|
30
|
+
class ParseError < StandardError; end
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
module AttrEl
|
|
33
|
+
# for backwards compatibility
|
|
34
|
+
def [] k; self.send(k.to_sym); end
|
|
35
|
+
def []= k, v; self.send("#{k}=".to_sym, v); end
|
|
21
36
|
end
|
|
22
|
-
|
|
37
|
+
|
|
23
38
|
# ignore the man behind the curtain.
|
|
24
39
|
def self.Multiple klass
|
|
25
40
|
Class.new(Array) do
|
|
26
41
|
@class = klass
|
|
27
42
|
|
|
28
|
-
def new
|
|
29
|
-
item = self.class.holds.new
|
|
43
|
+
def new *args
|
|
44
|
+
item = self.class.holds.new *args
|
|
30
45
|
self << item
|
|
31
|
-
|
|
46
|
+
|
|
32
47
|
item
|
|
33
48
|
end
|
|
34
49
|
|
|
35
|
-
def
|
|
36
|
-
|
|
50
|
+
def << item
|
|
51
|
+
raise ArgumentError, "this can only hold items of class #{self.class.holds}" unless item.is_a? self.class.holds
|
|
52
|
+
|
|
53
|
+
super(item)
|
|
37
54
|
end
|
|
38
55
|
|
|
39
56
|
def self.holds; @class end
|
|
@@ -42,183 +59,520 @@ module Atom # :nodoc:
|
|
|
42
59
|
end
|
|
43
60
|
end
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
# a
|
|
47
|
-
# and
|
|
48
|
-
|
|
62
|
+
module Parsers
|
|
63
|
+
# adds a parser that calls the given block for a single element that
|
|
64
|
+
# matches the given name and namespace (if it exists)
|
|
65
|
+
def on_parse name_pair, &block
|
|
66
|
+
uri, name = name_pair
|
|
67
|
+
@on_parse ||= []
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
process = lambda do |e,x|
|
|
70
|
+
el = e.get_elem(x, uri, name)
|
|
71
|
+
|
|
72
|
+
if el
|
|
73
|
+
block.call e, el
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
e.extensions.delete_if do |c|
|
|
76
|
+
c.namespace == uri and c.name == name.to_s
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
@on_parse << process
|
|
58
82
|
end
|
|
59
83
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
# adds a parser that calls the given block for the attribute that
|
|
85
|
+
# matches the given name (if it exists)
|
|
86
|
+
def on_parse_attr name_pair, &block
|
|
87
|
+
uri, name = name_pair
|
|
88
|
+
@on_parse ||= []
|
|
89
|
+
|
|
90
|
+
process = lambda do |e,x|
|
|
91
|
+
x = e.get_atom_attrb(x, name)
|
|
92
|
+
|
|
93
|
+
if x
|
|
94
|
+
block.call e, x
|
|
95
|
+
|
|
96
|
+
e.extensions.attributes.delete name.to_s
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
@on_parse << process
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# adds a parser that calls the given block for all elements
|
|
104
|
+
# that match the given name and namespace
|
|
105
|
+
def on_parse_many name_pair, &block
|
|
106
|
+
uri, name = name_pair
|
|
107
|
+
@on_parse ||= []
|
|
108
|
+
|
|
109
|
+
process = lambda do |e,x|
|
|
110
|
+
els = e.get_elems(x, uri, name)
|
|
111
|
+
|
|
112
|
+
unless els.empty?
|
|
113
|
+
block.call e, els
|
|
114
|
+
|
|
115
|
+
els.each do |el|
|
|
116
|
+
e.extensions.delete_if { |c| c.namespace == uri and c.name == name.to_s }
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@on_parse << process
|
|
63
122
|
end
|
|
64
123
|
|
|
65
|
-
#
|
|
66
|
-
def
|
|
67
|
-
@
|
|
124
|
+
# adds a parser that calls the given block for this element
|
|
125
|
+
def on_parse_root &block
|
|
126
|
+
@on_parse ||= []
|
|
127
|
+
|
|
128
|
+
process = lambda do |e,x|
|
|
129
|
+
block.call e, x
|
|
130
|
+
|
|
131
|
+
x.elements.each do |el|
|
|
132
|
+
e.extensions.clear
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@on_parse << process
|
|
68
137
|
end
|
|
69
138
|
|
|
70
|
-
#
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
139
|
+
# parses the text content of an element named 'name' into an attribute
|
|
140
|
+
# on this Element named 'name'
|
|
141
|
+
def parse_plain uri, name
|
|
142
|
+
self.on_parse [uri, name] do |e,x|
|
|
143
|
+
e.set(name, x.text)
|
|
74
144
|
end
|
|
75
|
-
|
|
76
|
-
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
module Converters
|
|
149
|
+
def build_plain ns, name
|
|
150
|
+
self.on_build do |e,x|
|
|
151
|
+
if v = e.get(name)
|
|
152
|
+
el = e.append_elem(x, ns, name)
|
|
153
|
+
el.text = v.to_s
|
|
154
|
+
end
|
|
77
155
|
end
|
|
78
156
|
end
|
|
79
157
|
|
|
80
|
-
#
|
|
81
|
-
def
|
|
158
|
+
# an element in the Atom namespace containing text
|
|
159
|
+
def atom_string(name)
|
|
160
|
+
attr_accessor name
|
|
161
|
+
|
|
162
|
+
self.parse_plain(Atom::NS, name)
|
|
163
|
+
self.build_plain(['atom', Atom::NS], name)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# an element in namespace 'ns' containing a RFC3339 timestamp
|
|
167
|
+
def time(ns, name)
|
|
82
168
|
attr_reader name
|
|
83
169
|
|
|
84
|
-
|
|
85
|
-
|
|
170
|
+
self.def_set name do |time|
|
|
171
|
+
unless time.respond_to? :iso8601
|
|
172
|
+
time = Time.parse(time.to_s)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def time.to_s; iso8601; end
|
|
86
176
|
|
|
87
|
-
|
|
88
|
-
self.define_accessor(name,kind)
|
|
177
|
+
instance_variable_set("@#{name}", time)
|
|
89
178
|
end
|
|
90
|
-
end
|
|
91
179
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
180
|
+
define_method "#{name}!".to_sym do
|
|
181
|
+
set(name, Time.now)
|
|
182
|
+
end
|
|
95
183
|
|
|
96
|
-
|
|
184
|
+
self.parse_plain(ns[1], name)
|
|
185
|
+
self.build_plain(ns, name)
|
|
97
186
|
end
|
|
98
|
-
|
|
99
|
-
#
|
|
100
|
-
def
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
187
|
+
|
|
188
|
+
# an element in the Atom namespace containing a timestamp
|
|
189
|
+
def atom_time(name)
|
|
190
|
+
self.time ['atom', Atom::NS], name
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# an element that is parsed by Element descendant 'klass'
|
|
194
|
+
def element(ns, name, klass)
|
|
195
|
+
el_name = name
|
|
196
|
+
name = name.to_s.gsub(/-/, '_')
|
|
197
|
+
|
|
198
|
+
attr_reader name
|
|
199
|
+
|
|
200
|
+
self.on_parse [ns[1], el_name] do |e,x|
|
|
201
|
+
e.instance_variable_set("@#{name}", klass.parse(x, e.base))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
self.on_build do |e,x|
|
|
205
|
+
if v = e.get(name)
|
|
206
|
+
el = e.append_elem(x, ns, el_name)
|
|
207
|
+
v.build(el)
|
|
108
208
|
end
|
|
109
|
-
|
|
110
|
-
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def_set name do |value|
|
|
212
|
+
instance_variable_set("@#{name}", klass.new(value))
|
|
111
213
|
end
|
|
112
214
|
end
|
|
113
215
|
|
|
114
|
-
#
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
super
|
|
216
|
+
# an element that is parsed by Element descendant 'klass'
|
|
217
|
+
def atom_element(name, klass)
|
|
218
|
+
self.element(['atom', Atom::NS], name, klass)
|
|
119
219
|
end
|
|
120
|
-
|
|
121
|
-
# set the value of an attribute
|
|
122
|
-
def []= key, value
|
|
123
|
-
test_key key
|
|
124
220
|
|
|
125
|
-
|
|
221
|
+
# an element that can appear multiple times that contains text
|
|
222
|
+
#
|
|
223
|
+
# 'one_name' is the name of the element, 'many_name' is the name of
|
|
224
|
+
# the attribute that will be created on this Element
|
|
225
|
+
def strings(ns, one_name, many_name)
|
|
226
|
+
attr_reader many_name
|
|
227
|
+
|
|
228
|
+
self.on_init do
|
|
229
|
+
instance_variable_set("@#{many_name}", [])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
self.on_parse_many [ns[1], one_name] do |e,xs|
|
|
233
|
+
var = e.instance_variable_get("@#{many_name}")
|
|
234
|
+
|
|
235
|
+
xs.each do |el|
|
|
236
|
+
var << el.text
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
self.on_build do |e,x|
|
|
241
|
+
e.instance_variable_get("@#{many_name}").each do |v|
|
|
242
|
+
e.append_elem(x, ns, one_name).text = v
|
|
243
|
+
end
|
|
244
|
+
end
|
|
126
245
|
end
|
|
127
246
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
247
|
+
# an element that can appear multiple times that is parsed by Element
|
|
248
|
+
# descendant 'klass'
|
|
249
|
+
#
|
|
250
|
+
# 'one_name' is the name of the element, 'many_name' is the name of
|
|
251
|
+
# the attribute that will be created on this Element
|
|
252
|
+
def elements(ns, one_name, many_name, klass)
|
|
253
|
+
attr_reader many_name
|
|
254
|
+
|
|
255
|
+
self.on_init do
|
|
256
|
+
var = Atom::Multiple(klass).new
|
|
257
|
+
instance_variable_set("@#{many_name}", var)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
self.on_parse_many [ns[1], one_name] do |e,xs|
|
|
261
|
+
var = e.get(many_name)
|
|
262
|
+
|
|
263
|
+
xs.each do |el|
|
|
264
|
+
var << klass.parse(el, e.base)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
132
267
|
|
|
133
|
-
self.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
268
|
+
self.on_build do |e,x|
|
|
269
|
+
e.get(many_name).each do |v|
|
|
270
|
+
el = e.append_elem(x, ns, one_name)
|
|
271
|
+
v.build(el)
|
|
137
272
|
end
|
|
138
273
|
end
|
|
139
274
|
end
|
|
140
275
|
|
|
141
|
-
#
|
|
142
|
-
def
|
|
143
|
-
|
|
276
|
+
# like #elements but in the Atom namespace
|
|
277
|
+
def atom_elements(one_name, many_name, klass)
|
|
278
|
+
self.elements(['atom', Atom::NS], one_name, many_name, klass)
|
|
144
279
|
end
|
|
145
|
-
|
|
146
|
-
# convert to a REXML::Element (with no namespace)
|
|
147
|
-
def to_element
|
|
148
|
-
elem = REXML::Element.new(local_name)
|
|
149
280
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
281
|
+
# an XML attribute in the namespace 'ns'
|
|
282
|
+
def attrb(ns, name)
|
|
283
|
+
attr_accessor name
|
|
284
|
+
|
|
285
|
+
self.on_parse_attr [ns[1], name] do |e,x|
|
|
286
|
+
e.set(name, x)
|
|
287
|
+
end
|
|
153
288
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
289
|
+
self.on_build do |e,x|
|
|
290
|
+
if v = e.get(name)
|
|
291
|
+
n = name.to_s
|
|
157
292
|
|
|
158
|
-
|
|
159
|
-
|
|
293
|
+
if x.namespace != ns[1]
|
|
294
|
+
x.add_namespace *ns unless x.namespaces[ns[0]]
|
|
295
|
+
n = "#{ns[0]}:#{n}"
|
|
160
296
|
end
|
|
297
|
+
|
|
298
|
+
x.attributes[n] = v.to_s
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# an XML attribute in the Atom namespace
|
|
304
|
+
def atom_attrb(name)
|
|
305
|
+
self.attrb(['atom', Atom::NS], name)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# a type of Atom Link. specifics defined by Hash 'criteria'
|
|
309
|
+
def atom_link name, criteria
|
|
310
|
+
def_get name do
|
|
311
|
+
existing = find_link(criteria)
|
|
312
|
+
|
|
313
|
+
existing and existing.href
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
def_set name do |value|
|
|
317
|
+
existing = find_link(criteria)
|
|
318
|
+
|
|
319
|
+
if existing
|
|
320
|
+
existing.href = value
|
|
161
321
|
else
|
|
162
|
-
|
|
322
|
+
links.new criteria.merge(:href => value)
|
|
163
323
|
end
|
|
164
324
|
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# The Class' methods provide a DSL for describing Atom's structure
|
|
329
|
+
# (and more generally for describing simple namespaced XML)
|
|
330
|
+
class Element
|
|
331
|
+
# this element's xml:base
|
|
332
|
+
attr_accessor :base
|
|
333
|
+
|
|
334
|
+
# xml elements and attributes that have been parsed, but are unknown
|
|
335
|
+
attr_reader :extensions
|
|
336
|
+
|
|
337
|
+
# attaches a name and a namespace to an element
|
|
338
|
+
# this needs to be called on any new element
|
|
339
|
+
def self.is_element ns, name
|
|
340
|
+
meta_def :self_namespace do; ns; end
|
|
341
|
+
meta_def :self_name do; name.to_s; end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# wrapper for #is_element
|
|
345
|
+
def self.is_atom_element name
|
|
346
|
+
self.is_element Atom::NS, name
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
# gets a single namespaced child element
|
|
350
|
+
def get_elem xml, ns, name
|
|
351
|
+
REXML::XPath.first xml, "./ns:#{name}", { 'ns' => ns }
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# gets multiple namespaced child elements
|
|
355
|
+
def get_elems xml, ns, name
|
|
356
|
+
REXML::XPath.match xml, "./ns:#{name}", { 'ns' => ns }
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# gets a child element in the Atom namespace
|
|
360
|
+
def get_atom_elem xml, name
|
|
361
|
+
get_elem xml, Atom::NS, name
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# gets multiple child elements in the Atom namespace
|
|
365
|
+
def get_atom_elems xml, name
|
|
366
|
+
get_elems Atom::NS, name
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
# gets an attribute on +xml+
|
|
370
|
+
def get_atom_attrb xml, name
|
|
371
|
+
xml.attributes[name.to_s]
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# sets an attribute on +xml+
|
|
375
|
+
def set_atom_attrb xml, name, value
|
|
376
|
+
xml.attributes[name.to_s] = value
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
extend Parsers
|
|
380
|
+
extend Converters
|
|
381
|
+
|
|
382
|
+
def self.on_build &block
|
|
383
|
+
@on_build ||= []
|
|
384
|
+
@on_build << block
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def self.do_parsing e, root
|
|
388
|
+
if ancestors[1].respond_to? :do_parsing
|
|
389
|
+
ancestors[1].do_parsing e, root
|
|
390
|
+
end
|
|
165
391
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
392
|
+
@on_parse ||= []
|
|
393
|
+
@on_parse.each { |p| p.call e, root }
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def self.builders &block
|
|
397
|
+
if ancestors[1].respond_to? :builders
|
|
398
|
+
ancestors[1].builders &block
|
|
169
399
|
end
|
|
170
400
|
|
|
171
|
-
|
|
172
|
-
|
|
401
|
+
@on_build ||= []
|
|
402
|
+
@on_build.each &block
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# turns a String, an IO-like, a REXML::Element, etc. into an Atom::Element
|
|
406
|
+
#
|
|
407
|
+
# the 'base' base URL parameter should be supplied if you know where this
|
|
408
|
+
# XML was fetched from
|
|
409
|
+
#
|
|
410
|
+
# if you want to parse into an existing Atom::Element, it can be passed in
|
|
411
|
+
# as 'element'
|
|
412
|
+
def self.parse xml, base = '', element = nil
|
|
413
|
+
if xml.respond_to? :elements
|
|
414
|
+
root = xml.dup
|
|
415
|
+
else
|
|
416
|
+
xml = xml.read if xml.respond_to? :read
|
|
417
|
+
|
|
418
|
+
begin
|
|
419
|
+
root = REXML::Document.new(xml.to_s).root
|
|
420
|
+
rescue REXML::ParseException => e
|
|
421
|
+
raise Atom::ParseError, e.message
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
unless root.local_name == self.self_name
|
|
426
|
+
raise Atom::ParseError, "expected element named #{self.self_name}, not #{root.local_name}"
|
|
173
427
|
end
|
|
174
428
|
|
|
175
|
-
|
|
176
|
-
|
|
429
|
+
unless root.namespace == self.self_namespace
|
|
430
|
+
raise Atom::ParseError, "expected element in namespace #{self.self_namespace}, not #{root.namespace}"
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
if root.attributes['xml:base']
|
|
434
|
+
base = (base.to_uri + root.attributes['xml:base'])
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
e = element ? element : self.new
|
|
438
|
+
e.base = base
|
|
439
|
+
|
|
440
|
+
# extension elements
|
|
441
|
+
root.elements.each do |c|
|
|
442
|
+
e.extensions << c
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# extension attributes
|
|
446
|
+
root.attributes.each do |k,v|
|
|
447
|
+
e.extensions.attributes[k] = v
|
|
177
448
|
end
|
|
178
449
|
|
|
179
|
-
|
|
450
|
+
# as things are parsed, they're removed from e.extensions. whatever's
|
|
451
|
+
# left over is stored so it can be round-tripped
|
|
452
|
+
|
|
453
|
+
self.do_parsing e, root
|
|
454
|
+
|
|
455
|
+
e
|
|
180
456
|
end
|
|
181
|
-
|
|
182
|
-
#
|
|
457
|
+
|
|
458
|
+
# converts to a REXML::Element
|
|
183
459
|
def to_xml
|
|
184
|
-
|
|
185
|
-
root
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
460
|
+
root = REXML::Element.new self.class.self_name
|
|
461
|
+
root.add_namespace self.class.self_namespace
|
|
462
|
+
|
|
463
|
+
build root
|
|
464
|
+
|
|
465
|
+
root
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
# fill a REXML::Element with the data from this Atom::Element
|
|
469
|
+
def build root
|
|
470
|
+
if self.base and not self.base.empty?
|
|
471
|
+
root.attributes['xml:base'] = self.base
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
self.class.builders do |builder|
|
|
475
|
+
builder.call self, root
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
@extensions.each do |e|
|
|
479
|
+
root << e.dup
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@extensions.attributes.each do |k,v|
|
|
483
|
+
root.attributes[k] = v
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
|
|
192
487
|
def to_s
|
|
193
488
|
to_xml.to_s
|
|
194
489
|
end
|
|
195
|
-
|
|
196
|
-
private
|
|
197
490
|
|
|
198
|
-
#
|
|
199
|
-
def
|
|
200
|
-
|
|
201
|
-
|
|
491
|
+
# defines a getter that calls 'block'
|
|
492
|
+
def self.def_get(name, &block)
|
|
493
|
+
define_method name.to_sym, &block
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# defines a setter that calls 'block'
|
|
497
|
+
def self.def_set(name, &block)
|
|
498
|
+
define_method "#{name}=".to_sym, &block
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# be sure to call #super if you override this method!
|
|
502
|
+
def initialize defaults = {}
|
|
503
|
+
@extensions = []
|
|
504
|
+
|
|
505
|
+
@extensions.instance_variable_set('@attrs', {})
|
|
506
|
+
def @extensions.attributes
|
|
507
|
+
@attrs
|
|
202
508
|
end
|
|
509
|
+
|
|
510
|
+
self.class.run_initters do |init|
|
|
511
|
+
self.instance_eval &init
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
defaults.each do |k,v|
|
|
515
|
+
set(k, v)
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
def self.on_init &block
|
|
520
|
+
@on_init ||= []
|
|
521
|
+
@on_init << block
|
|
203
522
|
end
|
|
204
523
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
self.class.attrs.find { |name,req| name.to_s == key }
|
|
524
|
+
def self.run_initters &block
|
|
525
|
+
@on_init.each(&block) if @on_init
|
|
208
526
|
end
|
|
209
527
|
|
|
528
|
+
# appends an element named 'name' in namespace 'ns' to 'root'
|
|
529
|
+
# ns is either [prefix, namespace] or just a String containing the namespace
|
|
530
|
+
def append_elem(root, ns, name)
|
|
531
|
+
if ns.is_a? Array
|
|
532
|
+
prefix, uri = ns
|
|
533
|
+
else
|
|
534
|
+
prefix, uri = nil, ns
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
name = name.to_s
|
|
538
|
+
|
|
539
|
+
existing_prefix = root.namespaces.find do |k,v|
|
|
540
|
+
v == uri
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
root << if existing_prefix
|
|
544
|
+
prefix = existing_prefix[0]
|
|
545
|
+
|
|
546
|
+
if prefix != 'xmlns'
|
|
547
|
+
name = prefix + ':' + name
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
REXML::Element.new(name)
|
|
551
|
+
elsif prefix
|
|
552
|
+
e = REXML::Element.new(prefix + ':' + name)
|
|
553
|
+
e.add_namespace(prefix, uri)
|
|
554
|
+
e
|
|
555
|
+
else
|
|
556
|
+
e = REXML::Element.new(name)
|
|
557
|
+
e.add_namespace(uri)
|
|
558
|
+
e
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def base= uri # :nodoc:
|
|
563
|
+
@base = uri.to_s
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
# calls a getter
|
|
210
567
|
def get name
|
|
211
|
-
|
|
568
|
+
send "#{name}".to_sym
|
|
212
569
|
end
|
|
213
570
|
|
|
571
|
+
# calls a setter
|
|
214
572
|
def set name, value
|
|
215
|
-
|
|
573
|
+
send "#{name}=", value
|
|
216
574
|
end
|
|
217
575
|
end
|
|
218
|
-
|
|
219
|
-
# this facilitates YAML output
|
|
220
|
-
class AttrEl < Atom::Element # :nodoc:
|
|
221
|
-
end
|
|
222
576
|
|
|
223
577
|
# A link has the following attributes:
|
|
224
578
|
#
|
|
@@ -228,31 +582,47 @@ module Atom # :nodoc:
|
|
|
228
582
|
# hreflang:: the language of the linked item (RFC3066)
|
|
229
583
|
# title:: human-readable information about the link
|
|
230
584
|
# length:: a hint about the length (in octets) of the linked item
|
|
231
|
-
class Link < Atom::
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
585
|
+
class Link < Atom::Element
|
|
586
|
+
is_atom_element :link
|
|
587
|
+
|
|
588
|
+
atom_attrb :href
|
|
589
|
+
atom_attrb :rel
|
|
590
|
+
atom_attrb :type
|
|
591
|
+
atom_attrb :hreflang
|
|
592
|
+
atom_attrb :title
|
|
593
|
+
atom_attrb :length
|
|
238
594
|
|
|
239
|
-
|
|
240
|
-
super name
|
|
595
|
+
include AttrEl
|
|
241
596
|
|
|
242
|
-
|
|
243
|
-
|
|
597
|
+
def rel
|
|
598
|
+
@rel or 'alternate'
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def self.parse xml, base = ''
|
|
602
|
+
e = super
|
|
603
|
+
|
|
604
|
+
# URL absolutization
|
|
605
|
+
if !e.base.empty? and e.href
|
|
606
|
+
e.href = (e.base.to_uri + e.href).to_s
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
e
|
|
244
610
|
end
|
|
245
611
|
end
|
|
246
|
-
|
|
612
|
+
|
|
247
613
|
# A category has the following attributes:
|
|
248
614
|
#
|
|
249
615
|
# term (required):: a string that identifies the category
|
|
250
616
|
# scheme:: an IRI that identifies a categorization scheme
|
|
251
617
|
# label:: a human-readable label
|
|
252
|
-
class Category < Atom::
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
618
|
+
class Category < Atom::Element
|
|
619
|
+
is_atom_element :category
|
|
620
|
+
|
|
621
|
+
atom_attrb :term
|
|
622
|
+
atom_attrb :scheme
|
|
623
|
+
atom_attrb :label
|
|
624
|
+
|
|
625
|
+
include AttrEl
|
|
256
626
|
end
|
|
257
627
|
|
|
258
628
|
# A person construct has the following child elements:
|
|
@@ -260,18 +630,56 @@ module Atom # :nodoc:
|
|
|
260
630
|
# name (required):: a human-readable name
|
|
261
631
|
# uri:: an IRI associated with the person
|
|
262
632
|
# email:: an email address associated with the person
|
|
263
|
-
class
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
633
|
+
class Person < Atom::Element
|
|
634
|
+
atom_string :name
|
|
635
|
+
atom_string :uri
|
|
636
|
+
atom_string :email
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
class Author < Atom::Person
|
|
640
|
+
is_atom_element :author
|
|
641
|
+
end
|
|
642
|
+
|
|
643
|
+
class Contributor < Atom::Person
|
|
644
|
+
is_atom_element :contributor
|
|
267
645
|
end
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
646
|
+
|
|
647
|
+
module HasLinks
|
|
648
|
+
def HasLinks.included(klass)
|
|
649
|
+
klass.atom_elements :link, :links, Atom::Link
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def find_link(criteria)
|
|
653
|
+
self.links.find do |l|
|
|
654
|
+
criteria.all? { |k,v| l.send(k) == v }
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
module HasCategories
|
|
660
|
+
def HasCategories.included(klass)
|
|
661
|
+
klass.atom_elements :category, :categories, Atom::Category
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
# categorize the entry with each of an array or a space-separated
|
|
665
|
+
# string
|
|
666
|
+
def tag_with(tags, delimiter = ' ')
|
|
667
|
+
return if not tags or tags.empty?
|
|
668
|
+
|
|
669
|
+
tag_list = unless tags.is_a?(String)
|
|
670
|
+
tags
|
|
671
|
+
else
|
|
672
|
+
tags = tags.split(delimiter)
|
|
673
|
+
tags.map! { |t| t.strip }
|
|
674
|
+
tags.reject! { |t| t.empty? }
|
|
675
|
+
tags.uniq
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
tag_list.each do |tag|
|
|
679
|
+
unless categories.any? { |c| c.term == tag }
|
|
680
|
+
categories.new :term => tag
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
end
|
|
276
684
|
end
|
|
277
685
|
end
|