ratom 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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