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.
Files changed (49) hide show
  1. data/.gitignore +4 -0
  2. data/History.txt +118 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +295 -0
  5. data/Rakefile +55 -0
  6. data/VERSION.yml +5 -0
  7. data/lib/atom/configuration.rb +24 -0
  8. data/lib/atom/pub.rb +253 -0
  9. data/lib/atom/version.rb +7 -0
  10. data/lib/atom/xml/parser.rb +376 -0
  11. data/lib/atom.rb +771 -0
  12. data/ratom-ssl.gemspec +98 -0
  13. data/spec/app/member_entry.atom +31 -0
  14. data/spec/app/service.xml +36 -0
  15. data/spec/atom/pub_spec.rb +504 -0
  16. data/spec/atom_spec.rb +1336 -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 +7 -0
  38. data/spec/fixtures/entry_with_simple_extensions.atom +30 -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 +43 -0
  49. metadata +140 -0
data/lib/atom.rb ADDED
@@ -0,0 +1,771 @@
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
+ case xml['type']
174
+ when "xhtml"
175
+ Xhtml.new(xml)
176
+ when "html"
177
+ Html.new(xml)
178
+ else
179
+ Text.new(xml)
180
+ end
181
+ end
182
+
183
+ # This is the base class for all content within an atom document.
184
+ #
185
+ # Content can be Text, Html or Xhtml.
186
+ #
187
+ # A Content object can be treated as a String with type and xml_lang
188
+ # attributes.
189
+ #
190
+ # For a thorough discussion of atom content see
191
+ # http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.content
192
+ class Base < DelegateClass(String)
193
+ include Xml::Parseable
194
+
195
+ def initialize(c)
196
+ __setobj__(c)
197
+ end
198
+
199
+ def ==(o)
200
+ if o.is_a?(self.class)
201
+ self.type == o.type &&
202
+ self.xml_lang == o.xml_lang &&
203
+ self.to_s == o.to_s
204
+ elsif o.is_a?(String)
205
+ self.to_s == o
206
+ end
207
+ end
208
+
209
+ protected
210
+ def set_content(c) # :nodoc:
211
+ __setobj__(c)
212
+ end
213
+ end
214
+
215
+ # Text content within an Atom document.
216
+ class Text < Base
217
+ attribute :type, :'xml:lang'
218
+ def initialize(o)
219
+ case o
220
+ when String
221
+ super(o)
222
+ @type = "text"
223
+ when XML::Reader
224
+ super(o.read_string)
225
+ parse(o, :once => true)
226
+ else
227
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
228
+ end
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 << self.to_s
234
+ node
235
+ end
236
+ end
237
+
238
+ # Html content within an Atom document.
239
+ class Html < Base
240
+ attribute :type, :'xml:lang'
241
+ # Creates a new Content::Html.
242
+ #
243
+ # +o+:: An XML::Reader or a HTML string.
244
+ #
245
+ def initialize(o)
246
+ case o
247
+ when XML::Reader
248
+ super(o.read_string)
249
+ parse(o, :once => true)
250
+ when String
251
+ super(o)
252
+ @type = 'html'
253
+ else
254
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
255
+ end
256
+ end
257
+
258
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new) # :nodoc:
259
+ require 'iconv'
260
+ # Convert from utf-8 to utf-8 as a way of making sure the content is UTF-8.
261
+ #
262
+ # This is a pretty crappy way to do it but if we don't check libxml just
263
+ # fails silently and outputs the content element without any content. At
264
+ # least checking here and raising an exception gives the caller a chance
265
+ # to try and recitfy the situation.
266
+ #
267
+ begin
268
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
269
+ node << Iconv.iconv('utf-8', 'utf-8', self.to_s)
270
+ node['type'] = 'html'
271
+ node['xml:lang'] = self.xml_lang if self.xml_lang
272
+ node
273
+ rescue Iconv::IllegalSequence => e
274
+ raise SerializationError, "Content must be converted to UTF-8 before attempting to serialize to XML: #{e.message}."
275
+ end
276
+ end
277
+ end
278
+
279
+ # XHTML content within an Atom document.
280
+ class Xhtml < Base
281
+ XHTML = 'http://www.w3.org/1999/xhtml'
282
+ attribute :type, :'xml:lang'
283
+
284
+ def initialize(o)
285
+ case o
286
+ when String
287
+ super(o)
288
+ @type = "xhtml"
289
+ when XML::Reader
290
+ super("")
291
+ xml = o
292
+ parse(xml, :once => true)
293
+ starting_depth = xml.depth
294
+
295
+ # Get the next element - should be a div according to the atom spec
296
+ while xml.read && xml.node_type != XML::Reader::TYPE_ELEMENT; end
297
+
298
+ if xml.local_name == 'div' && xml.namespace_uri == XHTML
299
+ set_content(xml.read_inner_xml.strip.gsub(/\s+/, ' '))
300
+ else
301
+ set_content(xml.read_outer_xml)
302
+ end
303
+
304
+ # get back to the end of the element we were created with
305
+ while xml.read == 1 && xml.depth > starting_depth; end
306
+ else
307
+ raise ArgumentError, "Got #{o} which isn't a String or XML::Reader"
308
+ end
309
+ end
310
+
311
+ def to_xml(nodeonly = true, name = 'content', namespace = nil, namespace_map = Atom::Xml::NamespaceMap.new)
312
+ node = XML::Node.new("#{namespace_map.prefix(Atom::NAMESPACE, name)}")
313
+ node['type'] = 'xhtml'
314
+ node['xml:lang'] = self.xml_lang.to_s
315
+
316
+ div = XML::Node.new('div')
317
+ div['xmlns'] = XHTML
318
+
319
+ p = XML::Parser.string(to_s)
320
+ content = p.parse.root.copy(true)
321
+ div << content
322
+
323
+ node << div
324
+ node
325
+ end
326
+ end
327
+ end
328
+
329
+ # Represents a Source as defined by the Atom Syndication Format specification.
330
+ #
331
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.source
332
+ class Source
333
+ extend Forwardable
334
+ def_delegators :@links, :alternate, :self, :alternates, :enclosures
335
+ include Xml::Parseable
336
+
337
+ element :id
338
+ element :updated, :class => Time, :content_only => true
339
+ element :title, :subtitle, :class => Content
340
+ elements :authors, :contributors, :class => Person
341
+ elements :links
342
+
343
+ def initialize(o = {})
344
+ @authors, @contributors, @links = [], [], Links.new
345
+
346
+ case o
347
+ when XML::Reader
348
+ unless current_node_is?(o, 'source', NAMESPACE)
349
+ raise ArgumentError, "Invalid node for atom:source - #{o.name}(#{o.namespace})"
350
+ end
351
+
352
+ o.read
353
+ parse(o)
354
+ when Hash
355
+ o.each do |k, v|
356
+ self.send("#{k.to_s}=", v)
357
+ end
358
+ else
359
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
360
+ end
361
+
362
+ yield(self) if block_given?
363
+ end
364
+ end
365
+
366
+ # Represents a Feed as defined by the Atom Syndication Format specification.
367
+ #
368
+ # A feed is the top level element in an atom document. It is a container for feed level
369
+ # metadata and for each entry in the feed.
370
+ #
371
+ # This supports pagination as defined in RFC 5005, see http://www.ietf.org/rfc/rfc5005.txt
372
+ #
373
+ # == Parsing
374
+ #
375
+ # A feed can be parsed using the Feed.load_feed method. This method accepts a String containing
376
+ # a valid atom document, an IO object, or an URI to a valid atom document. For example:
377
+ #
378
+ # # Using a File
379
+ # feed = Feed.load_feed(File.open("/path/to/myfeed.atom"))
380
+ #
381
+ # # Using a URL
382
+ # feed = Feed.load_feed(URI.parse("http://example.org/afeed.atom"))
383
+ #
384
+ # == Encoding
385
+ #
386
+ # A feed can be converted to XML using, the to_xml method that returns a valid atom document in a String.
387
+ #
388
+ # == Attributes
389
+ #
390
+ # A feed has the following attributes:
391
+ #
392
+ # +id+:: A unique id for the feed.
393
+ # +updated+:: The Time the feed was updated.
394
+ # +title+:: The title of the feed.
395
+ # +subtitle+:: The subtitle of the feed.
396
+ # +authors+:: An array of Atom::Person objects that are authors of this feed.
397
+ # +contributors+:: An array of Atom::Person objects that are contributors to this feed.
398
+ # +generator+:: A Atom::Generator.
399
+ # +categories+:: A list of Atom:Category objects for the feed.
400
+ # +rights+:: A string describing the rights associated with this feed.
401
+ # +entries+:: An array of Atom::Entry objects.
402
+ # +links+:: An array of Atom:Link objects. (This is actually an Atom::Links array which is an Array with some sugar).
403
+ #
404
+ # == References
405
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.feed
406
+ #
407
+ class Feed
408
+ include Xml::Parseable
409
+ include SimpleExtensions
410
+ extend Forwardable
411
+ def_delegators :@links, :alternate, :self, :via, :first_page, :last_page, :next_page, :prev_page
412
+
413
+ loadable!
414
+
415
+ namespace Atom::NAMESPACE
416
+ element :id, :rights
417
+ element :generator, :class => Generator
418
+ element :title, :subtitle, :class => Content
419
+ element :updated, :class => Time, :content_only => true
420
+ elements :links
421
+ elements :authors, :contributors, :class => Person
422
+ elements :categories
423
+ elements :entries
424
+
425
+ # Initialize a Feed.
426
+ #
427
+ # This will also yield itself, so a feed can be constructed like this:
428
+ #
429
+ # feed = Feed.new do |feed|
430
+ # feed.title = "My Cool feed"
431
+ # end
432
+ #
433
+ # +o+:: An XML Reader or a Hash of attributes.
434
+ #
435
+ def initialize(o = {})
436
+ @links, @entries, @authors, @contributors, @categories = Links.new, [], [], [], []
437
+
438
+ case o
439
+ when XML::Reader
440
+ if next_node_is?(o, 'feed', Atom::NAMESPACE)
441
+ o.read
442
+ parse(o)
443
+ else
444
+ raise ArgumentError, "XML document was missing atom:feed: #{o.read_outer_xml}"
445
+ end
446
+ when Hash
447
+ o.each do |k, v|
448
+ self.send("#{k.to_s}=", v)
449
+ end
450
+ else
451
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
452
+ end
453
+
454
+ yield(self) if block_given?
455
+ end
456
+
457
+ # Return true if this is the first feed in a paginated set.
458
+ def first?
459
+ links.self == links.first_page
460
+ end
461
+
462
+ # Returns true if this is the last feed in a paginated set.
463
+ def last?
464
+ links.self == links.last_page
465
+ end
466
+
467
+ # Reloads the feed by fetching the self uri.
468
+ def reload!(opts = {})
469
+ if links.self
470
+ Feed.load_feed(URI.parse(links.self.href), opts)
471
+ end
472
+ end
473
+
474
+ # Iterates over each entry in the feed.
475
+ #
476
+ # ==== Options
477
+ #
478
+ # +paginate+:: If true and the feed supports pagination this will fetch each page of the feed.
479
+ # +since+:: If a Time object is provided each_entry will iterate over all entries that were updated since that time.
480
+ # +user+:: User name for HTTP Basic Authentication.
481
+ # +pass+:: Password for HTTP Basic Authentication.
482
+ #
483
+ def each_entry(options = {}, &block)
484
+ if options[:paginate]
485
+ since_reached = false
486
+ feed = self
487
+ loop do
488
+ feed.entries.each do |entry|
489
+ if options[:since] && entry.updated && options[:since] > entry.updated
490
+ since_reached = true
491
+ break
492
+ else
493
+ block.call(entry)
494
+ end
495
+ end
496
+
497
+ if since_reached || feed.next_page.nil?
498
+ break
499
+ else feed.next_page
500
+ feed = feed.next_page.fetch(options)
501
+ end
502
+ end
503
+ else
504
+ self.entries.each(&block)
505
+ end
506
+ end
507
+ end
508
+
509
+ # Represents an Entry as defined by the Atom Syndication Format specification.
510
+ #
511
+ # An Entry represents an individual entry within a Feed.
512
+ #
513
+ # == Parsing
514
+ #
515
+ # An Entry can be parsed using the Entry.load_entry method. This method accepts a String containing
516
+ # a valid atom entry document, an IO object, or an URI to a valid atom entry document. For example:
517
+ #
518
+ # # Using a File
519
+ # entry = Entry.load_entry(File.open("/path/to/myfeedentry.atom"))
520
+ #
521
+ # # Using a URL
522
+ # Entry = Entry.load_entry(URI.parse("http://example.org/afeedentry.atom"))
523
+ #
524
+ # The document must contain a stand alone entry element as described in the Atom Syndication Format.
525
+ #
526
+ # == Encoding
527
+ #
528
+ # A Entry can be converted to XML using, the to_xml method that returns a valid atom entry document in a String.
529
+ #
530
+ # == Attributes
531
+ #
532
+ # An entry has the following attributes:
533
+ #
534
+ # +id+:: A unique id for the entry.
535
+ # +updated+:: The Time the entry was updated.
536
+ # +published+:: The Time the entry was published.
537
+ # +title+:: The title of the entry.
538
+ # +summary+:: A short textual summary of the item.
539
+ # +authors+:: An array of Atom::Person objects that are authors of this entry.
540
+ # +contributors+:: An array of Atom::Person objects that are contributors to this entry.
541
+ # +rights+:: A string describing the rights associated with this entry.
542
+ # +links+:: An array of Atom:Link objects. (This is actually an Atom::Links array which is an Array with some sugar).
543
+ # +source+:: Metadata of a feed that was the source of this item, for feed aggregators, etc.
544
+ # +categories+:: Array of Atom::Categories.
545
+ # +content+:: The content of the entry. This will be one of Atom::Content::Text, Atom::Content:Html or Atom::Content::Xhtml.
546
+ #
547
+ # == References
548
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.entry for more detailed
549
+ # definitions of the attributes.
550
+ #
551
+ class Entry
552
+ include Xml::Parseable
553
+ include SimpleExtensions
554
+ extend Forwardable
555
+ def_delegators :@links, :alternate, :self, :alternates, :enclosures, :edit_link, :via
556
+
557
+ loadable!
558
+ namespace Atom::NAMESPACE
559
+ element :title, :id, :summary
560
+ element :updated, :published, :class => Time, :content_only => true
561
+ element :source, :class => Source
562
+ elements :links
563
+ elements :authors, :contributors, :class => Person
564
+ elements :categories, :class => Category
565
+ element :content, :class => Content
566
+
567
+ # Initialize an Entry.
568
+ #
569
+ # This will also yield itself, so an Entry can be constructed like this:
570
+ #
571
+ # entry = Entry.new do |entry|
572
+ # entry.title = "My Cool entry"
573
+ # end
574
+ #
575
+ # +o+:: An XML Reader or a Hash of attributes.
576
+ #
577
+ def initialize(o = {})
578
+ @links = Links.new
579
+ @authors = []
580
+ @contributors = []
581
+ @categories = []
582
+
583
+ case o
584
+ when XML::Reader
585
+ if current_node_is?(o, 'entry', Atom::NAMESPACE) || next_node_is?(o, 'entry', Atom::NAMESPACE)
586
+ o.read
587
+ parse(o)
588
+ else
589
+ raise ArgumentError, "Entry created with node other than atom:entry: #{o.name}"
590
+ end
591
+ when Hash
592
+ o.each do |k,v|
593
+ send("#{k.to_s}=", v)
594
+ end
595
+ else
596
+ raise ArgumentError, "Got #{o.class} but expected a Hash or XML::Reader"
597
+ end
598
+
599
+ yield(self) if block_given?
600
+ end
601
+
602
+ # Reload the Entry by fetching the self link.
603
+ def reload!(opts = {})
604
+ if links.self
605
+ Entry.load_entry(URI.parse(links.self.href), opts)
606
+ end
607
+ end
608
+ end
609
+
610
+ # Links provides an Array of Link objects belonging to either a Feed or an Entry.
611
+ #
612
+ # Some additional methods to get specific types of links are provided.
613
+ #
614
+ # == References
615
+ #
616
+ # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link
617
+ # for details on link selection and link attributes.
618
+ #
619
+ class Links < DelegateClass(Array)
620
+ include Enumerable
621
+
622
+ # Initialize an empty Links array.
623
+ def initialize
624
+ super([])
625
+ end
626
+
627
+ # Get the alternate.
628
+ #
629
+ # Returns the first link with rel == 'alternate' that matches the given type.
630
+ def alternate(type = nil)
631
+ detect { |link|
632
+ (link.rel.nil? || link.rel == Link::Rel::ALTERNATE) && (type.nil? || type == link.type) && (link.hreflang.nil?)
633
+ } || detect { |link|
634
+ (link.rel.nil? || link.rel == Link::Rel::ALTERNATE) && (type.nil? || type == link.type)
635
+ }
636
+ end
637
+
638
+ # Get all alternates.
639
+ def alternates
640
+ select { |link| link.rel.nil? || link.rel == Link::Rel::ALTERNATE }
641
+ end
642
+
643
+ # Gets the self link.
644
+ def self
645
+ detect { |link| link.rel == Link::Rel::SELF }
646
+ end
647
+
648
+ # Gets the via link.
649
+ def via
650
+ detect { |link| link.rel == Link::Rel::VIA }
651
+ end
652
+
653
+ # Gets all links with rel == 'enclosure'
654
+ def enclosures
655
+ select { |link| link.rel == Link::Rel::ENCLOSURE }
656
+ end
657
+
658
+ # Gets the link with rel == 'first'.
659
+ #
660
+ # This is defined as the first page in a pagination set.
661
+ def first_page
662
+ detect { |link| link.rel == Link::Rel::FIRST }
663
+ end
664
+
665
+ # Gets the link with rel == 'last'.
666
+ #
667
+ # This is defined as the last page in a pagination set.
668
+ def last_page
669
+ detect { |link| link.rel == Link::Rel::LAST }
670
+ end
671
+
672
+ # Gets the link with rel == 'next'.
673
+ #
674
+ # This is defined as the next page in a pagination set.
675
+ def next_page
676
+ detect { |link| link.rel == Link::Rel::NEXT }
677
+ end
678
+
679
+ # Gets the link with rel == 'prev'.
680
+ #
681
+ # This is defined as the previous page in a pagination set.
682
+ def prev_page
683
+ detect { |link| link.rel == Link::Rel::PREVIOUS }
684
+ end
685
+
686
+ # Gets the edit link.
687
+ #
688
+ # This is the link which can be used for posting updates to an item using the Atom Publishing Protocol.
689
+ #
690
+ def edit_link
691
+ detect { |link| link.rel == 'edit' }
692
+ end
693
+ end
694
+
695
+ # Represents a link in an Atom document.
696
+ #
697
+ # A link defines a reference from an Atom document to a web resource.
698
+ #
699
+ # == References
700
+ # See http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link for
701
+ # a description of the different types of links.
702
+ #
703
+ class Link
704
+ module Rel # :nodoc:
705
+ ALTERNATE = 'alternate'
706
+ SELF = 'self'
707
+ VIA = 'via'
708
+ ENCLOSURE = 'enclosure'
709
+ FIRST = 'first'
710
+ LAST = 'last'
711
+ PREVIOUS = 'prev'
712
+ NEXT = 'next'
713
+ end
714
+
715
+ include Xml::Parseable
716
+ attribute :href, :rel, :type, :length, :hreflang, :title
717
+
718
+ # Create a link.
719
+ #
720
+ # +o+:: An XML::Reader containing a link element or a Hash of attributes.
721
+ #
722
+ def initialize(o)
723
+ case o
724
+ when XML::Reader
725
+ if current_node_is?(o, 'link')
726
+ parse(o, :once => true)
727
+ else
728
+ raise ArgumentError, "Link created with node other than atom:link: #{o.name}"
729
+ end
730
+ when Hash
731
+ [:href, :rel, :type, :length, :hreflang, :title].each do |attr|
732
+ self.send("#{attr}=", o[attr])
733
+ end
734
+ else
735
+ raise ArgumentError, "Don't know how to handle #{o}"
736
+ end
737
+ end
738
+
739
+ remove_method :length=
740
+ def length=(v)
741
+ @length = v.to_i
742
+ end
743
+
744
+ def to_s
745
+ self.href
746
+ end
747
+
748
+ def ==(o)
749
+ o.respond_to?(:href) && o.href == self.href
750
+ end
751
+
752
+ # This will fetch the URL referenced by the link.
753
+ #
754
+ # If the URL contains a valid feed, a Feed will be returned, otherwise,
755
+ # the body of the response will be returned.
756
+ #
757
+ # TODO: Handle redirects.
758
+ #
759
+ def fetch(options = {})
760
+ begin
761
+ Atom::Feed.load_feed(URI.parse(self.href), options)
762
+ rescue ArgumentError
763
+ Net::HTTP.get_response(URI.parse(self.href)).body
764
+ end
765
+ end
766
+
767
+ def inspect
768
+ "<Atom::Link href:'#{href}' type:'#{type}'>"
769
+ end
770
+ end
771
+ end