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,5 @@
1
+ ---
2
+ :major: 0
3
+ :build:
4
+ :minor: 6
5
+ :patch: 9
@@ -0,0 +1,796 @@
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 'forwardable'
9
+ require 'delegate'
10
+ require 'rubygems'
11
+ gem 'libxml-ruby', '>= 1.1.2'
12
+ require 'xml/libxml'
13
+ require 'atom/xml/parser.rb'
14
+
15
+ module Atom # :nodoc:
16
+ NAMESPACE = 'http://www.w3.org/2005/Atom' unless defined?(NAMESPACE)
17
+ module Pub
18
+ NAMESPACE = 'http://www.w3.org/2007/app'
19
+ end
20
+
21
+ # Raised when a Serialization Error occurs.
22
+ class SerializationError < StandardError; end
23
+
24
+ # Provides support for reading and writing simple extensions as defined by the Atom Syndication Format.
25
+ #
26
+ # A Simple extension is an element from a non-atom namespace that has no attributes and only contains
27
+ # text content. It is interpreted as a key-value pair when the namespace and the localname of the
28
+ # extension make up the key. Since in XML you can have many instances of an element, the values are
29
+ # represented as an array of strings, so to manipulate the values manipulate the array returned by
30
+ # +[ns, localname]+.
31
+ #
32
+ module SimpleExtensions
33
+ attr_reader :simple_extensions
34
+
35
+ # Gets a simple extension value for a given namespace and local name.
36
+ #
37
+ # +ns+:: The namespace.
38
+ # +localname+:: The local name of the extension element.
39
+ #
40
+ def [](namespace, localname=nil)
41
+ @simple_extensions ||= {}
42
+
43
+ localname.nil? ? namespace_hash(namespace) : element_values(namespace, localname)
44
+ end
45
+
46
+ protected
47
+
48
+ def namespace_hash(namespace)
49
+ namespace_keys = @simple_extensions.keys.select { |key| key =~ /^\{#{namespace},/ }
50
+
51
+ elements = {}
52
+ namespace_keys.each do |key|
53
+ attribute_name = key.match(/\{.*,(.*)\}/)[1]
54
+ elements[attribute_name] = @simple_extensions[key]
55
+ end
56
+ elements
57
+ end
58
+
59
+ def element_values(namespace, localname)
60
+ key = "{#{namespace},#{localname}}"
61
+ @simple_extensions[key] ||= ValueProxy.new
62
+ end
63
+
64
+ class ValueProxy < DelegateClass(Array)
65
+ attr_accessor :as_attribute
66
+ def initialize
67
+ super([])
68
+ @as_attribute = false
69
+ end
70
+ end
71
+ end
72
+
73
+ # Represents a Generator as defined by the Atom Syndication Format specification.
74
+ #
75
+ # The generator identifies an agent or engine used to a produce a feed.
76
+ #
77
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.generator
78
+ class Generator
79
+ include Xml::Parseable
80
+
81
+ attr_accessor :name
82
+ attribute :uri, :version
83
+
84
+ # Initialize a new Generator.
85
+ #
86
+ # +xml+:: An XML::Reader object.
87
+ #
88
+ def initialize(o = {})
89
+ case o
90
+ when XML::Reader
91
+ @name = o.read_string.strip
92
+ parse(o, :once => true)
93
+ when Hash
94
+ o.each do |k, v|
95
+ self.send("#{k.to_s}=", v)
96
+ end
97
+ else
98
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
99
+ end
100
+
101
+ yield(self) if block_given?
102
+ end
103
+
104
+ def to_xml(nodeonly = true, name = 'generator', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new)
105
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
106
+ node << @name if @name
107
+ node['uri'] = @uri if @uri
108
+ node['version'] = @version if @version
109
+ node
110
+ end
111
+ end
112
+
113
+ # Represents a Category as defined by the Atom Syndication Format specification.
114
+ #
115
+ #
116
+ class Category
117
+ include Atom::Xml::Parseable
118
+ include SimpleExtensions
119
+ attribute :label, :scheme, :term
120
+
121
+ def initialize(o = {})
122
+ case o
123
+ when XML::Reader
124
+ parse(o, :once => true)
125
+ when Hash
126
+ o.each do |k, v|
127
+ self.send("#{k.to_s}=", v)
128
+ end
129
+ else
130
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
131
+ end
132
+
133
+ yield(self) if block_given?
134
+ end
135
+ end
136
+
137
+ # Represents a Person as defined by the Atom Syndication Format specification.
138
+ #
139
+ # A Person is used for all author and contributor attributes.
140
+ #
141
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#atomPersonConstruct
142
+ #
143
+ class Person
144
+ include Xml::Parseable
145
+ element :name, :uri, :email
146
+
147
+ # Initialize a new person.
148
+ #
149
+ # +o+:: An XML::Reader object or a hash. Valid hash keys are +:name+, +:uri+ and +:email+.
150
+ def initialize(o = {})
151
+ case o
152
+ when XML::Reader
153
+ o.read
154
+ parse(o)
155
+ when Hash
156
+ o.each do |k, v|
157
+ self.send("#{k.to_s}=", v)
158
+ end
159
+ else
160
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
161
+ end
162
+
163
+ yield(self) if block_given?
164
+ end
165
+
166
+ def inspect
167
+ "<Atom::Person name:'#{name}' uri:'#{uri}' email:'#{email}"
168
+ end
169
+ end
170
+
171
+ class Content # :nodoc:
172
+ def self.parse(xml)
173
+ if xml['src'] && !xml['src'].empty?
174
+ External.new(xml)
175
+ else
176
+ case xml['type']
177
+ when "xhtml"
178
+ Xhtml.new(xml)
179
+ when "html"
180
+ Html.new(xml)
181
+ else
182
+ Text.new(xml)
183
+ end
184
+ end
185
+ end
186
+
187
+ # This is the base class for all content within an atom document.
188
+ #
189
+ # Content can be Text, Html or Xhtml.
190
+ #
191
+ # A Content object can be treated as a String with type and xml_lang
192
+ # attributes.
193
+ #
194
+ # For a thorough discussion of atom content see
195
+ # http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.content
196
+ class Base < DelegateClass(String)
197
+ include Xml::Parseable
198
+
199
+ def initialize(c)
200
+ __setobj__(c)
201
+ end
202
+
203
+ def ==(o)
204
+ if o.is_a?(self.class)
205
+ self.type == o.type &&
206
+ self.xml_lang == o.xml_lang &&
207
+ self.to_s == o.to_s
208
+ elsif o.is_a?(String)
209
+ self.to_s == o
210
+ end
211
+ end
212
+
213
+ protected
214
+ def set_content(c) # :nodoc:
215
+ __setobj__(c)
216
+ end
217
+ end
218
+
219
+ # External content reference within an Atom document.
220
+ class External < Base
221
+ attribute :type, :src
222
+
223
+ # this one only works with XML::Reader instances, no strings, since the
224
+ # content won't be inline.
225
+ def initialize(o)
226
+ raise ArgumentError, "Got #{o} which isn't an XML::Reader" unless o.kind_of?(XML::Reader)
227
+ super("")
228
+ parse(o, :once => true)
229
+ end
230
+
231
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new)
232
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
233
+ node['type'] = self.type if self.type
234
+ node['src'] = self.src if self.src
235
+ node
236
+ end
237
+ end
238
+
239
+ # Text content within an Atom document.
240
+ class Text < Base
241
+ attribute :type, :'xml:lang'
242
+ def initialize(o)
243
+ case o
244
+ when String
245
+ super(o)
246
+ @type = "text"
247
+ when XML::Reader
248
+ super(o.read_string)
249
+ parse(o, :once => true)
250
+ else
251
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
252
+ end
253
+ end
254
+
255
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new)
256
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
257
+ node << self.to_s
258
+ node
259
+ end
260
+ end
261
+
262
+ # Html content within an Atom document.
263
+ class Html < Base
264
+ attribute :type, :'xml:lang'
265
+ # Creates a new Content::Html.
266
+ #
267
+ # +o+:: An XML::Reader or a HTML string.
268
+ #
269
+ def initialize(o)
270
+ case o
271
+ when XML::Reader
272
+ super(o.read_string)
273
+ parse(o, :once => true)
274
+ when String
275
+ super(o)
276
+ @type = 'html'
277
+ else
278
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
279
+ end
280
+ end
281
+
282
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new) # :nodoc:
283
+ require 'iconv'
284
+ # Convert from utf-8 to utf-8 as a way of making sure the content is UTF-8.
285
+ #
286
+ # This is a pretty crappy way to do it but if we don't check libxml just
287
+ # fails silently and outputs the content element without any content. At
288
+ # least checking here and raising an exception gives the caller a chance
289
+ # to try and recitfy the situation.
290
+ #
291
+ begin
292
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
293
+ node << Iconv.iconv('utf-8', 'utf-8', self.to_s).first
294
+ node['type'] = 'html'
295
+ node['xml:lang'] = self.xml_lang if self.xml_lang
296
+ node
297
+ rescue Iconv::IllegalSequence => e
298
+ raise SerializationError, "Content must be converted to UTF-8 before attempting to serialize to XML: #{e.message}."
299
+ end
300
+ end
301
+ end
302
+
303
+ # XHTML content within an Atom document.
304
+ class Xhtml < Base
305
+ XHTML = 'http://www.w3.org/1999/xhtml'
306
+ attribute :type, :'xml:lang'
307
+
308
+ def initialize(o)
309
+ case o
310
+ when String
311
+ super(o)
312
+ @type = "xhtml"
313
+ when XML::Reader
314
+ super("")
315
+ xml = o
316
+ parse(xml, :once => true)
317
+ starting_depth = xml.depth
318
+
319
+ # Get the next element - should be a div according to the atom spec
320
+ while xml.read && xml.node_type != XML::Reader::TYPE_ELEMENT; end
321
+
322
+ if xml.local_name == 'div' && xml.namespace_uri == XHTML
323
+ set_content(xml.read_inner_xml.strip.gsub(/\s+/, ' '))
324
+ else
325
+ set_content(xml.read_outer_xml)
326
+ end
327
+
328
+ # get back to the end of the element we were created with
329
+ while xml.read == 1 && xml.depth > starting_depth; end
330
+ else
331
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
332
+ end
333
+ end
334
+
335
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new)
336
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
337
+ node['type'] = 'xhtml'
338
+ node['xml:lang'] = self.xml_lang.to_s
339
+
340
+ div = XML::Node.new('div')
341
+ div['xmlns'] = XHTML
342
+
343
+ p = XML::Parser.string(to_s)
344
+ content = p.parse.root.copy(true)
345
+ div << content
346
+
347
+ node << div
348
+ node
349
+ end
350
+ end
351
+ end
352
+
353
+ # Represents a Source as defined by the Atom Syndication Format specification.
354
+ #
355
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.source
356
+ class Source
357
+ extend Forwardable
358
+ def_delegators :@links, :alternate, :self, :alternates, :enclosures
359
+ include Xml::Parseable
360
+
361
+ element :id
362
+ element :updated, :class => Time, :content_only => true
363
+ element :title, :subtitle, :class => Content
364
+ elements :authors, :contributors, :class => Person
365
+ elements :links
366
+
367
+ def initialize(o = {})
368
+ @authors, @contributors, @links = [], [], Links.new
369
+
370
+ case o
371
+ when XML::Reader
372
+ unless current_node_is?(o, 'source', NAMESPACE)
373
+ raise ArgumentError, "Invalid node for atom:source - #{o.name}(#{o.namespace})"
374
+ end
375
+
376
+ o.read
377
+ parse(o)
378
+ when Hash
379
+ o.each do |k, v|
380
+ self.send("#{k.to_s}=", v)
381
+ end
382
+ else
383
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
384
+ end
385
+
386
+ yield(self) if block_given?
387
+ end
388
+ end
389
+
390
+ # Represents a Feed as defined by the Atom Syndication Format specification.
391
+ #
392
+ # A feed is the top level element in an atom document. It is a container for feed level
393
+ # metadata and for each entry in the feed.
394
+ #
395
+ # This supports pagination as defined in RFC 5005, see http://www.ietf.org/rfc/rfc5005.txt
396
+ #
397
+ # == Parsing
398
+ #
399
+ # A feed can be parsed using the Feed.load_feed method. This method accepts a String containing
400
+ # a valid atom document, an IO object, or an URI to a valid atom document. For example:
401
+ #
402
+ # # Using a File
403
+ # feed = Feed.load_feed(File.open("/path/to/myfeed.atom"))
404
+ #
405
+ # # Using a URL
406
+ # feed = Feed.load_feed(URI.parse("http://example.org/afeed.atom"))
407
+ #
408
+ # == Encoding
409
+ #
410
+ # A feed can be converted to XML using, the to_xml method that returns a valid atom document in a String.
411
+ #
412
+ # == Attributes
413
+ #
414
+ # A feed has the following attributes:
415
+ #
416
+ # +id+:: A unique id for the feed.
417
+ # +updated+:: The Time the feed was updated.
418
+ # +title+:: The title of the feed.
419
+ # +subtitle+:: The subtitle of the feed.
420
+ # +authors+:: An array of Atom::Person objects that are authors of this feed.
421
+ # +contributors+:: An array of Atom::Person objects that are contributors to this feed.
422
+ # +generator+:: A Atom::Generator.
423
+ # +categories+:: A list of Atom:Category objects for the feed.
424
+ # +rights+:: A string describing the rights associated with this feed.
425
+ # +entries+:: An array of Atom::Entry objects.
426
+ # +links+:: An array of Atom:Link objects. (This is actually an Atom::Links array which is an Array with some sugar).
427
+ #
428
+ # == References
429
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.feed
430
+ #
431
+ class Feed
432
+ include Xml::Parseable
433
+ include SimpleExtensions
434
+ extend Forwardable
435
+ def_delegators :@links, :alternate, :self, :via, :first_page, :last_page, :next_page, :prev_page
436
+
437
+ loadable!
438
+
439
+ namespace Atom::NAMESPACE
440
+ element :id, :rights
441
+ element :generator, :class => Generator
442
+ element :title, :subtitle, :class => Content
443
+ element :updated, :class => Time, :content_only => true
444
+ elements :links
445
+ elements :authors, :contributors, :class => Person
446
+ elements :categories
447
+ elements :entries
448
+
449
+ # Initialize a Feed.
450
+ #
451
+ # This will also yield itself, so a feed can be constructed like this:
452
+ #
453
+ # feed = Feed.new do |feed|
454
+ # feed.title = "My Cool feed"
455
+ # end
456
+ #
457
+ # +o+:: An XML Reader or a Hash of attributes.
458
+ #
459
+ def initialize(o = {})
460
+ @links, @entries, @authors, @contributors, @categories = Links.new, [], [], [], []
461
+
462
+ case o
463
+ when XML::Reader
464
+ if next_node_is?(o, 'feed', Atom::NAMESPACE)
465
+ o.read
466
+ parse(o)
467
+ else
468
+ raise ArgumentError, "XML document was missing atom:feed: #{o.read_outer_xml}"
469
+ end
470
+ when Hash
471
+ o.each do |k, v|
472
+ self.send("#{k.to_s}=", v)
473
+ end
474
+ else
475
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
476
+ end
477
+
478
+ yield(self) if block_given?
479
+ end
480
+
481
+ # Return true if this is the first feed in a paginated set.
482
+ def first?
483
+ links.self == links.first_page
484
+ end
485
+
486
+ # Returns true if this is the last feed in a paginated set.
487
+ def last?
488
+ links.self == links.last_page
489
+ end
490
+
491
+ # Reloads the feed by fetching the self uri.
492
+ def reload!(opts = {})
493
+ if links.self
494
+ Feed.load_feed(URI.parse(links.self.href), opts)
495
+ end
496
+ end
497
+
498
+ # Iterates over each entry in the feed.
499
+ #
500
+ # ==== Options
501
+ #
502
+ # +paginate+:: If true and the feed supports pagination this will fetch each page of the feed.
503
+ # +since+:: If a Time object is provided each_entry will iterate over all entries that were updated since that time.
504
+ # +user+:: User name for HTTP Basic Authentication.
505
+ # +pass+:: Password for HTTP Basic Authentication.
506
+ #
507
+ def each_entry(options = {}, &block)
508
+ if options[:paginate]
509
+ since_reached = false
510
+ feed = self
511
+ loop do
512
+ feed.entries.each do |entry|
513
+ if options[:since] && entry.updated && options[:since] > entry.updated
514
+ since_reached = true
515
+ break
516
+ else
517
+ block.call(entry)
518
+ end
519
+ end
520
+
521
+ if since_reached || feed.next_page.nil?
522
+ break
523
+ else feed.next_page
524
+ feed = feed.next_page.fetch(options)
525
+ end
526
+ end
527
+ else
528
+ self.entries.each(&block)
529
+ end
530
+ end
531
+ end
532
+
533
+ # Represents an Entry as defined by the Atom Syndication Format specification.
534
+ #
535
+ # An Entry represents an individual entry within a Feed.
536
+ #
537
+ # == Parsing
538
+ #
539
+ # An Entry can be parsed using the Entry.load_entry method. This method accepts a String containing
540
+ # a valid atom entry document, an IO object, or an URI to a valid atom entry document. For example:
541
+ #
542
+ # # Using a File
543
+ # entry = Entry.load_entry(File.open("/path/to/myfeedentry.atom"))
544
+ #
545
+ # # Using a URL
546
+ # Entry = Entry.load_entry(URI.parse("http://example.org/afeedentry.atom"))
547
+ #
548
+ # The document must contain a stand alone entry element as described in the Atom Syndication Format.
549
+ #
550
+ # == Encoding
551
+ #
552
+ # A Entry can be converted to XML using, the to_xml method that returns a valid atom entry document in a String.
553
+ #
554
+ # == Attributes
555
+ #
556
+ # An entry has the following attributes:
557
+ #
558
+ # +id+:: A unique id for the entry.
559
+ # +updated+:: The Time the entry was updated.
560
+ # +published+:: The Time the entry was published.
561
+ # +title+:: The title of the entry.
562
+ # +summary+:: A short textual summary of the item.
563
+ # +authors+:: An array of Atom::Person objects that are authors of this entry.
564
+ # +contributors+:: An array of Atom::Person objects that are contributors to this entry.
565
+ # +rights+:: A string describing the rights associated with this entry.
566
+ # +links+:: An array of Atom:Link objects. (This is actually an Atom::Links array which is an Array with some sugar).
567
+ # +source+:: Metadata of a feed that was the source of this item, for feed aggregators, etc.
568
+ # +categories+:: Array of Atom::Categories.
569
+ # +content+:: The content of the entry. This will be one of Atom::Content::Text, Atom::Content:Html or Atom::Content::Xhtml.
570
+ #
571
+ # == References
572
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.entry for more detailed
573
+ # definitions of the attributes.
574
+ #
575
+ class Entry
576
+ include Xml::Parseable
577
+ include SimpleExtensions
578
+ extend Forwardable
579
+ def_delegators :@links, :alternate, :self, :alternates, :enclosures, :edit_link, :via
580
+
581
+ loadable!
582
+ namespace Atom::NAMESPACE
583
+ element :title, :id, :summary
584
+ element :updated, :published, :class => Time, :content_only => true
585
+ element :source, :class => Source
586
+ elements :links
587
+ elements :authors, :contributors, :class => Person
588
+ elements :categories, :class => Category
589
+ element :content, :class => Content
590
+
591
+ # Initialize an Entry.
592
+ #
593
+ # This will also yield itself, so an Entry can be constructed like this:
594
+ #
595
+ # entry = Entry.new do |entry|
596
+ # entry.title = "My Cool entry"
597
+ # end
598
+ #
599
+ # +o+:: An XML Reader or a Hash of attributes.
600
+ #
601
+ def initialize(o = {})
602
+ @links = Links.new
603
+ @authors = []
604
+ @contributors = []
605
+ @categories = []
606
+
607
+ case o
608
+ when XML::Reader
609
+ if current_node_is?(o, 'entry', Atom::NAMESPACE) || next_node_is?(o, 'entry', Atom::NAMESPACE)
610
+ o.read
611
+ parse(o)
612
+ else
613
+ raise ArgumentError, "Entry created with node other than atom:entry: #{o.name}"
614
+ end
615
+ when Hash
616
+ o.each do |k,v|
617
+ send("#{k.to_s}=", v)
618
+ end
619
+ else
620
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
621
+ end
622
+
623
+ yield(self) if block_given?
624
+ end
625
+
626
+ # Reload the Entry by fetching the self link.
627
+ def reload!(opts = {})
628
+ if links.self
629
+ Entry.load_entry(URI.parse(links.self.href), opts)
630
+ end
631
+ end
632
+ end
633
+
634
+ # Links provides an Array of Link objects belonging to either a Feed or an Entry.
635
+ #
636
+ # Some additional methods to get specific types of links are provided.
637
+ #
638
+ # == References
639
+ #
640
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link
641
+ # for details on link selection and link attributes.
642
+ #
643
+ class Links < DelegateClass(Array)
644
+ include Enumerable
645
+
646
+ # Initialize an empty Links array.
647
+ def initialize
648
+ super([])
649
+ end
650
+
651
+ # Get the alternate.
652
+ #
653
+ # Returns the first link with rel == 'alternate' that matches the given type.
654
+ def alternate(type = nil)
655
+ detect { |link|
656
+ (link.rel.nil? || link.rel == Link::Rel::ALTERNATE) && (type.nil? || type == link.type) && (link.hreflang.nil?)
657
+ } || detect { |link|
658
+ (link.rel.nil? || link.rel == Link::Rel::ALTERNATE) && (type.nil? || type == link.type)
659
+ }
660
+ end
661
+
662
+ # Get all alternates.
663
+ def alternates
664
+ select { |link| link.rel.nil? || link.rel == Link::Rel::ALTERNATE }
665
+ end
666
+
667
+ # Gets the self link.
668
+ def self
669
+ detect { |link| link.rel == Link::Rel::SELF }
670
+ end
671
+
672
+ # Gets the via link.
673
+ def via
674
+ detect { |link| link.rel == Link::Rel::VIA }
675
+ end
676
+
677
+ # Gets all links with rel == 'enclosure'
678
+ def enclosures
679
+ select { |link| link.rel == Link::Rel::ENCLOSURE }
680
+ end
681
+
682
+ # Gets the link with rel == 'first'.
683
+ #
684
+ # This is defined as the first page in a pagination set.
685
+ def first_page
686
+ detect { |link| link.rel == Link::Rel::FIRST }
687
+ end
688
+
689
+ # Gets the link with rel == 'last'.
690
+ #
691
+ # This is defined as the last page in a pagination set.
692
+ def last_page
693
+ detect { |link| link.rel == Link::Rel::LAST }
694
+ end
695
+
696
+ # Gets the link with rel == 'next'.
697
+ #
698
+ # This is defined as the next page in a pagination set.
699
+ def next_page
700
+ detect { |link| link.rel == Link::Rel::NEXT }
701
+ end
702
+
703
+ # Gets the link with rel == 'prev'.
704
+ #
705
+ # This is defined as the previous page in a pagination set.
706
+ def prev_page
707
+ detect { |link| link.rel == Link::Rel::PREVIOUS }
708
+ end
709
+
710
+ # Gets the edit link.
711
+ #
712
+ # This is the link which can be used for posting updates to an item using the Atom Publishing Protocol.
713
+ #
714
+ def edit_link
715
+ detect { |link| link.rel == 'edit' }
716
+ end
717
+ end
718
+
719
+ # Represents a link in an Atom document.
720
+ #
721
+ # A link defines a reference from an Atom document to a web resource.
722
+ #
723
+ # == References
724
+ # See http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link for
725
+ # a description of the different types of links.
726
+ #
727
+ class Link
728
+ module Rel # :nodoc:
729
+ ALTERNATE = 'alternate'
730
+ SELF = 'self'
731
+ VIA = 'via'
732
+ ENCLOSURE = 'enclosure'
733
+ FIRST = 'first'
734
+ LAST = 'last'
735
+ PREVIOUS = 'prev'
736
+ NEXT = 'next'
737
+ end
738
+
739
+ include Xml::Parseable
740
+ attribute :rel, :type, :length, :hreflang, :title
741
+ uri_attribute :href
742
+
743
+ # Create a link.
744
+ #
745
+ # +o+:: An XML::Reader containing a link element or a Hash of attributes.
746
+ #
747
+ def initialize(o)
748
+ case o
749
+ when XML::Reader
750
+ if current_node_is?(o, 'link')
751
+ parse(o, :once => true)
752
+ else
753
+ raise ArgumentError, "Link created with node other than atom:link: #{o.name}"
754
+ end
755
+ when Hash
756
+ [:href, :rel, :type, :length, :hreflang, :title].each do |attr|
757
+ self.send("#{attr}=", o[attr])
758
+ end
759
+ else
760
+ raise ArgumentError, "Don't know how to handle #{o}"
761
+ end
762
+ end
763
+
764
+ remove_method :length=
765
+ def length=(v)
766
+ @length = v.to_i
767
+ end
768
+
769
+ def to_s
770
+ self.href
771
+ end
772
+
773
+ def ==(o)
774
+ o.respond_to?(:href) && o.href == self.href
775
+ end
776
+
777
+ # This will fetch the URL referenced by the link.
778
+ #
779
+ # If the URL contains a valid feed, a Feed will be returned, otherwise,
780
+ # the body of the response will be returned.
781
+ #
782
+ # TODO: Handle redirects.
783
+ #
784
+ def fetch(options = {})
785
+ begin
786
+ Atom::Feed.load_feed(URI.parse(self.href), options)
787
+ rescue ArgumentError
788
+ Net::HTTP.get_response(URI.parse(self.href)).body
789
+ end
790
+ end
791
+
792
+ def inspect
793
+ "<Atom::Link href:'#{href}' type:'#{type}'>"
794
+ end
795
+ end
796
+ end