ratom 0.2.2 → 0.3.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/History.txt CHANGED
@@ -1,3 +1,9 @@
1
+ == 0.3.0 2008-04-02
2
+
3
+ * Raise Atom::Serialization error when you try and serialize non-utf8 content.
4
+ * Support reading simple extension elements, see README.txt.
5
+ * Support writing simple extension elements, see README.txt.
6
+
1
7
  == 0.2.2 2008-02-10
2
8
 
3
9
  * Removed dependency on ActiveSupport.
data/README.txt CHANGED
@@ -97,6 +97,45 @@ required to let us update to the entry. For example, lets change the content an
97
97
 
98
98
  You can also delete an entry using the <tt>destroy!</tt> method, but we won't do that will we?.
99
99
 
100
+ === Extension elements
101
+
102
+ As of version 0.3.0, rAtom support simple extension elements on feeds and entries. As defined in the Atom Syndication Format,
103
+ simple extension elements consist of XML elements from a non-Atom namespace that have no attributes or child elements, i.e.
104
+ they are empty or only contain text content. These elements are treated as a name value pair where the element namespace
105
+ and local name make up the key and the content of the element is the value, empty elements will be treated as an empty string.
106
+
107
+ To access extension elements use the [] method on the Feed or Entry. For example, if we are parsing the follow Atom document
108
+ with extensions:
109
+
110
+ <?xml version="1.0"?>
111
+ <feed xmlns="http://www.w3.org/2005/Atom" xmlns:ex="http://example.org">
112
+ <title>Feed with extensions</title>
113
+ <ex:myelement>Something important</ex:myelement>
114
+ </feed>
115
+
116
+ We could then access the extension element on the feed using:
117
+
118
+ > feed["http://example.org", "myelement"]
119
+ => ["Something important"]
120
+
121
+ Note that the return value is an array. This is because XML allows multiple instances of the element.
122
+
123
+ To set an extension element you append to the array:
124
+
125
+ > feed['http://example.org', 'myelement'] << 'Something less important'
126
+ => ["Something important", "Something less important"]
127
+
128
+ You can then call to_xml and rAtom will serialize the extension elements into xml.
129
+
130
+ > puts feed.to_xml
131
+ <?xml version="1.0"?>
132
+ <feed xmlns="http://www.w3.org/2005/Atom">
133
+ <myelement xmlns="http://example.org">Something important</myelement>
134
+ <myelement xmlns="http://example.org">Something less important</myelement>
135
+ </feed>
136
+
137
+ Notice that the output repeats the xmlns attribute for each of the extensions, this is semantically the same the input XML, just a bit
138
+ ugly. It seems to be a limitation of the libxml-Ruby API. But if anyone knows a work around I'd gladly accept a patch (or even advice).
100
139
 
101
140
  == TODO
102
141
 
@@ -105,8 +144,8 @@ You can also delete an entry using the <tt>destroy!</tt> method, but we won't do
105
144
  * Examples of editing existing entries.
106
145
  * All my tests have been against internal systems, I'd really like feedback from those who have tried rAtom using existing blog software that supports APP.
107
146
  * Handle all base uri tests.
108
- * What to do with extension elements?
109
147
  * Add slug support.
148
+ * Handle HTTP basic authentication.
110
149
 
111
150
  == Source Code
112
151
 
data/lib/atom.rb CHANGED
@@ -11,10 +11,40 @@ require 'atom/xml/parser.rb'
11
11
 
12
12
  module Atom # :nodoc:
13
13
  NAMESPACE = 'http://www.w3.org/2005/Atom' unless defined?(NAMESPACE)
14
-
14
+ module Pub
15
+ NAMESPACE = 'http://www.w3.org/2007/app'
16
+ end
15
17
  # Raised when a Parsing Error occurs.
16
18
  class ParseError < StandardError; end
19
+ # Raised when a Serialization Error occurs.
20
+ class SerializationError < StandardError; end
21
+
22
+ # Provides support for reading and writing simple extensions as defined by the Atom Syndication Format.
23
+ #
24
+ # A Simple extension is an element from a non-atom namespace that has no attributes and only contains
25
+ # text content. It is interpreted as a key-value pair when the namespace and the localname of the
26
+ # extension make up the key. Since in XML you can have many instances of an element, the values are
27
+ # represented as an array of strings, so to manipulate the values manipulate the array returned by
28
+ # +[ns, localname]+.
29
+ #
30
+ module SimpleExtensions
31
+ attr_reader :simple_extensions
32
+
33
+ # Gets a simple extension value for a given namespace and local name.
34
+ #
35
+ # +ns+:: The namespace.
36
+ # +localname+:: The local name of the extension element.
37
+ #
38
+ def [](ns, localname)
39
+ if @simple_extensions.nil?
40
+ @simple_extensions = {}
41
+ end
17
42
 
43
+ key = "{#{ns},#{localname}}"
44
+ (@simple_extensions[key] or @simple_extensions[key] = [])
45
+ end
46
+ end
47
+
18
48
  # Represents a Generator as defined by the Atom Syndication Format specification.
19
49
  #
20
50
  # The generator identifies an agent or engine used to a produce a feed.
@@ -138,11 +168,23 @@ module Atom # :nodoc:
138
168
  end
139
169
 
140
170
  def to_xml(nodeonly = true, name = 'content') # :nodoc:
141
- node = XML::Node.new(name)
142
- node << self.to_s
143
- node['type'] = 'html'
144
- node['xml:lang'] = self.xml_lang
145
- node
171
+ require 'iconv'
172
+ # Convert from utf-8 to utf-8 as a way of making sure the content is UTF-8.
173
+ #
174
+ # This is a pretty crappy way to do it but if we don't check libxml just
175
+ # fails silently and outputs the content element without any content. At
176
+ # least checking here and raising an exception gives the caller a chance
177
+ # to try and recitfy the situation.
178
+ #
179
+ begin
180
+ node = XML::Node.new(name)
181
+ node << Iconv.iconv('utf-8', 'utf-8', self.to_s)
182
+ node['type'] = 'html'
183
+ node['xml:lang'] = self.xml_lang
184
+ node
185
+ rescue Iconv::IllegalSequence => e
186
+ raise SerializationError, "Content must be converted to UTF-8 before attempting to serialize to XML: #{e.message}."
187
+ end
146
188
  end
147
189
  end
148
190
 
@@ -239,9 +281,10 @@ module Atom # :nodoc:
239
281
  # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.feed
240
282
  class Feed
241
283
  include Xml::Parseable
284
+ include SimpleExtensions
242
285
  extend Forwardable
243
286
  def_delegators :@links, :alternate, :self, :via, :first_page, :last_page, :next_page, :prev_page
244
-
287
+
245
288
  loadable!
246
289
 
247
290
  element :id, :rights
@@ -327,7 +370,7 @@ module Atom # :nodoc:
327
370
  else
328
371
  self.entries.each(&block)
329
372
  end
330
- end
373
+ end
331
374
  end
332
375
 
333
376
  # Represents an Entry as defined by the Atom Syndication Format specification.
@@ -355,6 +398,7 @@ module Atom # :nodoc:
355
398
  # See also http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.entry
356
399
  class Entry
357
400
  include Xml::Parseable
401
+ include SimpleExtensions
358
402
  extend Forwardable
359
403
  def_delegators :@links, :alternate, :self, :alternates, :enclosures, :edit_link, :via
360
404
 
data/lib/atom/pub.rb CHANGED
@@ -21,7 +21,6 @@ module Atom
21
21
  @response = response
22
22
  end
23
23
  end
24
- NAMESPACE = 'http://www.w3.org/2007/app'
25
24
 
26
25
  class Service
27
26
  include Atom::Xml::Parseable
data/lib/atom/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Atom #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
5
- TINY = 2
4
+ MINOR = 3
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -6,6 +6,7 @@
6
6
  #
7
7
 
8
8
  require 'net/http'
9
+ require 'time'
9
10
 
10
11
  # Just a couple methods form transforming strings
11
12
  unless defined?(ActiveSupport)
@@ -36,7 +37,7 @@ module Atom
36
37
  loop do
37
38
  case xml.node_type
38
39
  when XML::Reader::TYPE_ELEMENT
39
- if element_specs.include?(xml.local_name)
40
+ if element_specs.include?(xml.local_name) && [Atom::NAMESPACE, Atom::Pub::NAMESPACE].include?(xml.namespace_uri)
40
41
  element_specs[xml.local_name].parse(self, xml)
41
42
  elsif attributes.any?
42
43
  while (xml.move_to_next_attribute == 1)
@@ -45,6 +46,8 @@ module Atom
45
46
  self.send("#{xml.name.sub(/:/, '_')}=", xml.value)
46
47
  end
47
48
  end
49
+ elsif self.respond_to?(:simple_extensions)
50
+ self[xml.namespace_uri, xml.local_name] << xml.read_string
48
51
  end
49
52
  end
50
53
  break unless !options[:once] && xml.next == 1 && xml.depth >= starting_depth
@@ -114,6 +117,21 @@ module Atom
114
117
  end
115
118
  end
116
119
 
120
+ if self.respond_to?(:simple_extensions) && self.simple_extensions
121
+ self.simple_extensions.each do |name, value_array|
122
+ if name =~ /\{(.*),(.*)\}/
123
+ value_array.each do |value|
124
+ ext = XML::Node.new($2)
125
+ ext['xmlns'] = $1
126
+ ext << value
127
+ node << ext
128
+ end
129
+ else
130
+ STDERR.print "Couldn't split #{name}"
131
+ end
132
+ end
133
+ end
134
+
117
135
  unless nodeonly
118
136
  doc = XML::Document.new
119
137
  doc.root = node
data/spec/atom_spec.rb CHANGED
@@ -7,6 +7,77 @@
7
7
 
8
8
  require File.dirname(__FILE__) + '/spec_helper.rb'
9
9
  require 'net/http'
10
+ require 'time'
11
+
12
+ shared_examples_for 'simple_single_entry.atom attributes' do
13
+ it "should parse title" do
14
+ @feed.title.should == 'Example Feed'
15
+ end
16
+
17
+ it "should parse updated" do
18
+ @feed.updated.should == Time.parse('2003-12-13T18:30:02Z')
19
+ end
20
+
21
+ it "should parse id" do
22
+ @feed.id.should == 'urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6'
23
+ end
24
+
25
+ it "should have an entries array" do
26
+ @feed.entries.should be_an_instance_of(Array)
27
+ end
28
+
29
+ it "should have one element in the entries array" do
30
+ @feed.entries.size.should == 1
31
+ end
32
+
33
+ it "should have an alternate" do
34
+ @feed.alternate.should_not be_nil
35
+ end
36
+
37
+ it "should have an Atom::Link as the alternate" do
38
+ @feed.alternate.should be_an_instance_of(Atom::Link)
39
+ end
40
+
41
+ it "should have the correct href in the alternate" do
42
+ @feed.alternate.href.should == 'http://example.org/'
43
+ end
44
+
45
+ it "should parse title" do
46
+ @entry.title.should == 'Atom-Powered Robots Run Amok'
47
+ end
48
+
49
+ it "should have an alternate" do
50
+ @entry.alternate.should_not be_nil
51
+ end
52
+
53
+ it "should have an Atom::Link as the alternate" do
54
+ @entry.alternate.should be_an_instance_of(Atom::Link)
55
+ end
56
+
57
+ it "should have the correct href on the alternate" do
58
+ @entry.alternate.href.should == 'http://example.org/2003/12/13/atom03'
59
+ end
60
+
61
+ it "should parse id" do
62
+ @entry.id.should == 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'
63
+ end
64
+
65
+ it "should parse updated" do
66
+ @entry.updated.should == Time.parse('2003-12-13T18:30:02Z')
67
+ end
68
+
69
+ it "should parse summary" do
70
+ @entry.summary.should == 'Some text.'
71
+ end
72
+
73
+ it "should parse content" do
74
+ @entry.content.should == 'This <em>is</em> html.'
75
+ end
76
+
77
+ it "should parse content type" do
78
+ @entry.content.type.should == 'html'
79
+ end
80
+ end
10
81
 
11
82
  describe Atom do
12
83
  describe "Atom::Feed.load_feed" do
@@ -70,83 +141,10 @@ describe Atom do
70
141
  describe 'SimpleSingleFeed' do
71
142
  before(:all) do
72
143
  @feed = Atom::Feed.load_feed(File.open('spec/fixtures/simple_single_entry.atom'))
144
+ @entry = @feed.entries.first
73
145
  end
74
146
 
75
- describe Atom::Feed do
76
- it "should parse title" do
77
- @feed.title.should == 'Example Feed'
78
- end
79
-
80
- it "should parse updated" do
81
- @feed.updated.should == Time.parse('2003-12-13T18:30:02Z')
82
- end
83
-
84
- it "should parse id" do
85
- @feed.id.should == 'urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6'
86
- end
87
-
88
- it "should have an entries array" do
89
- @feed.entries.should be_an_instance_of(Array)
90
- end
91
-
92
- it "should have one element in the entries array" do
93
- @feed.entries.size.should == 1
94
- end
95
-
96
- it "should have an alternate" do
97
- @feed.alternate.should_not be_nil
98
- end
99
-
100
- it "should have an Atom::Link as the alternate" do
101
- @feed.alternate.should be_an_instance_of(Atom::Link)
102
- end
103
-
104
- it "should have the correct href in the alternate" do
105
- @feed.alternate.href.should == 'http://example.org/'
106
- end
107
- end
108
-
109
- describe Atom::Entry do
110
- before(:each) do
111
- @entry = @feed.entries.first
112
- end
113
-
114
- it "should parse title" do
115
- @entry.title.should == 'Atom-Powered Robots Run Amok'
116
- end
117
-
118
- it "should have an alternate" do
119
- @entry.alternate.should_not be_nil
120
- end
121
-
122
- it "should have an Atom::Link as the alternate" do
123
- @entry.alternate.should be_an_instance_of(Atom::Link)
124
- end
125
-
126
- it "should have the correct href on the alternate" do
127
- @entry.alternate.href.should == 'http://example.org/2003/12/13/atom03'
128
- end
129
-
130
- it "should parse id" do
131
- @entry.id.should == 'urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a'
132
- end
133
-
134
- it "should parse updated" do
135
- @entry.updated.should == Time.parse('2003-12-13T18:30:02Z')
136
- end
137
-
138
- it "should parse summary" do
139
- @entry.summary.should == 'Some text.'
140
- end
141
-
142
- it "should parse content" do
143
- @entry.content.should == 'This <em>is</em> html.'
144
- end
145
-
146
- it "should parse content type" do
147
- @entry.content.type.should == 'html'
148
- end
149
- end
147
+ it_should_behave_like "simple_single_entry.atom attributes"
150
148
  end
151
149
 
152
150
  describe 'ComplexFeed' do
@@ -905,6 +903,53 @@ describe Atom do
905
903
  entry_count.should == 1
906
904
  end
907
905
  end
906
+
907
+ describe "entry_with_simple_extensions.atom" do
908
+ before(:each) do
909
+ @feed = Atom::Feed.load_feed(File.open('spec/fixtures/entry_with_simple_extensions.atom'))
910
+ @entry = @feed.entries.first
911
+ end
912
+
913
+ it "should load simple extension for feed" do
914
+ @feed["http://example.org/example", 'simple1'].should == ['Simple1 Value']
915
+ end
916
+
917
+ it "should load empty simple extension for feed" do
918
+ @feed["http://example.org/example", 'simple-empty'].should == ['']
919
+ end
920
+
921
+ it "should load simple extension 1 for entry" do
922
+ @entry["http://example.org/example", 'simple1'].should == ['Simple1 Entry Value']
923
+ end
924
+
925
+ it "should load simple extension 2 for entry" do
926
+ @entry["http://example.org/example", 'simple2'].should == ['Simple2', 'Simple2a']
927
+ end
928
+
929
+ it "should find a simple extension in another namespace" do
930
+ @entry["http://example2.org/example2", 'simple1'].should == ['Simple Entry Value (NS2)']
931
+ end
932
+
933
+ it "should read an extension with the same local name as an Atom element" do
934
+ @feed['http://example.org/example', 'title'].should == ['Extension Title']
935
+ end
936
+
937
+ it_should_behave_like 'simple_single_entry.atom attributes'
938
+ end
939
+
940
+ describe 'writing simple extensions' do
941
+ it "should recode and re-read a simple extension element" do
942
+ entry = Atom::Entry.new do |entry|
943
+ entry.id = 'urn:test'
944
+ entry.title = 'Simple Ext. Test'
945
+ entry.updated = Time.now
946
+ entry['http://example.org', 'title'] << 'Example title'
947
+ end
948
+
949
+ entry2 = Atom::Entry.load_entry(entry.to_xml)
950
+ entry2['http://example.org', 'title'].should == ['Example title']
951
+ end
952
+ end
908
953
  end
909
954
 
910
955
  describe Atom::Link do
@@ -972,6 +1017,16 @@ describe Atom do
972
1017
  other = Atom::Entry.load_entry(@entry.to_xml)
973
1018
  @entry.should == other
974
1019
  end
1020
+
1021
+ it "should raise error when to_xml'ing non-utf8 content" do
1022
+ lambda {
1023
+ puts(Atom::Entry.new do |entry|
1024
+ entry.title = "My entry"
1025
+ entry.id = "urn:entry:1"
1026
+ entry.content = Atom::Content::Html.new("this is not \227 utf8")
1027
+ end.to_xml)
1028
+ }.should raise_error(Atom::SerializationError)
1029
+ end
975
1030
  end
976
1031
 
977
1032
  describe 'Atom::Feed initializer' do
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
5
- platform: ""
4
+ version: 0.3.0
5
+ platform: ruby
6
6
  authors:
7
7
  - Peerworks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-03-10 00:00:00 +10:30
12
+ date: 2008-04-02 00:00:00 +10:30
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  requirements: []
110
110
 
111
111
  rubyforge_project: ratom
112
- rubygems_version: 0.9.5
112
+ rubygems_version: 1.1.0
113
113
  signing_key:
114
114
  specification_version: 2
115
115
  summary: Atom Syndication and Publication API