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 +6 -0
- data/README.txt +40 -1
- data/lib/atom.rb +52 -8
- data/lib/atom/pub.rb +0 -1
- data/lib/atom/version.rb +2 -2
- data/lib/atom/xml/parser.rb +19 -1
- data/spec/atom_spec.rb +130 -75
- metadata +4 -4
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
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
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
data/lib/atom/version.rb
CHANGED
data/lib/atom/xml/parser.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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:
|
112
|
+
rubygems_version: 1.1.0
|
113
113
|
signing_key:
|
114
114
|
specification_version: 2
|
115
115
|
summary: Atom Syndication and Publication API
|