urss 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemrc +1 -0
- data/.gitignore +17 -0
- data/.gpairrc +1 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -0
- data/LICENSE +22 -0
- data/README.md +47 -0
- data/Rakefile +8 -0
- data/lib/urss.rb +23 -0
- data/lib/urss/feed.rb +17 -0
- data/lib/urss/feed/atom.rb +20 -0
- data/lib/urss/feed/atom_entry.rb +44 -0
- data/lib/urss/feed/entry.rb +20 -0
- data/lib/urss/feed/rss.rb +27 -0
- data/lib/urss/feed/rss_entry.rb +49 -0
- data/lib/urss/media.rb +31 -0
- data/lib/urss/rss.rb +28 -0
- data/lib/urss/version.rb +3 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/fixtures/atom.xml +45 -0
- data/spec/support/fixtures/media_rss.xml +465 -0
- data/spec/support/fixtures/not-rss.xml +8 -0
- data/spec/support/fixtures/rss09.rdf +79 -0
- data/spec/support/fixtures/rss20.xml +818 -0
- data/spec/support/fixtures/ruby.rss +395 -0
- data/spec/support/fixtures/stackoverflow.com.xml +1344 -0
- data/spec/support/fixtures/wax.rss +358 -0
- data/spec/support/webmocks.rb +6 -0
- data/spec/urss/feed/atom_entry_spec.rb +22 -0
- data/spec/urss/feed/atom_spec.rb +57 -0
- data/spec/urss/feed/entry_spec.rb +42 -0
- data/spec/urss/feed/rss_entry_spec.rb +22 -0
- data/spec/urss/feed/rss_spec.rb +79 -0
- data/spec/urss/feed_atom_spec.rb +27 -0
- data/spec/urss/feed_spec.rb +32 -0
- data/spec/urss/media_spec.rb +37 -0
- data/spec/urss/rss_spec.rb +25 -0
- data/spec/urss_spec.rb +540 -0
- data/urss.gemspec +19 -0
- metadata +129 -0
data/.gemrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
:ssl_verify_mode: 0
|
data/.gitignore
ADDED
data/.gpairrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
alone for github
|
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm --create use 1.9.3@urss > /dev/null
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 zedtux
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Urss [![Build Status](https://secure.travis-ci.org/zedtux/urss.png)](http://travis-ci.org/zedtux/urss) [![Dependency Status](https://gemnasium.com/zedtux/urss.png)](http://gemnasium.com/zedtux/urss)
|
2
|
+
|
3
|
+
URSS or Ultra RSS is another ruby library to parse Feed RSS that has been created because there was no existing one that support multiple media:content or media:thumbnail.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'urss'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install urss
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
````ruby
|
22
|
+
rss = Urss.at("http://www.ruby-lang.org/en/feeds/news.rss")
|
23
|
+
rss.title
|
24
|
+
#=> "Ruby News"
|
25
|
+
rss.url
|
26
|
+
#=> "http://www.ruby-lang.org/en/feeds/news.rss/"
|
27
|
+
rss.description
|
28
|
+
#=> "The latest news from Ruby-Lang.org."
|
29
|
+
rss.updated_at
|
30
|
+
#=> ""
|
31
|
+
rss.entries.size
|
32
|
+
#=> 10
|
33
|
+
rss.entries.first.title
|
34
|
+
#=> "Ruby 1.9.3-p194 is released"
|
35
|
+
rss.entries.first.created_at
|
36
|
+
#=> "Fri, 20 Apr 2012 03:19:04 GMT"
|
37
|
+
rss.entries.first.url
|
38
|
+
#=> "http://www.ruby-lang.org/en/news/2012/04/20/ruby-1-9-3-p194-is-released/"
|
39
|
+
````
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/urss.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require "urss/version"
|
2
|
+
require "open-uri"
|
3
|
+
require "nokogiri"
|
4
|
+
|
5
|
+
require "urss/rss"
|
6
|
+
require "urss/feed"
|
7
|
+
require "urss/feed/entry"
|
8
|
+
require "urss/feed/atom"
|
9
|
+
require "urss/feed/atom_entry"
|
10
|
+
require "urss/feed/rss"
|
11
|
+
require "urss/feed/rss_entry"
|
12
|
+
require "urss/media"
|
13
|
+
|
14
|
+
module Urss
|
15
|
+
class NotANokogiriInstance < StandardError; end
|
16
|
+
|
17
|
+
def self.at(url)
|
18
|
+
raise ArgumentError if url.nil? || !url.is_a?(String) || url.empty?
|
19
|
+
|
20
|
+
Rss.build(Nokogiri::XML(open(url)))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/lib/urss/feed.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Urss::Feed
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
attr_accessor :title, :url, :description, :updated_at, :entries
|
5
|
+
|
6
|
+
# ~~~~ Class methods ~~~~
|
7
|
+
|
8
|
+
# ~~~~ Instance methods ~~~~
|
9
|
+
def initialize
|
10
|
+
self.title = nil
|
11
|
+
self.url = nil
|
12
|
+
self.description = nil
|
13
|
+
self.updated_at = nil
|
14
|
+
self.entries = []
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Urss::Feed::Atom < Urss::Feed
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
|
5
|
+
# ~~~~ Class methods ~~~~
|
6
|
+
def self.build(nokogiri_instance, namespace, root_node)
|
7
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::NodeSet)
|
8
|
+
feed_rss = self.new
|
9
|
+
feed_rss.title = nokogiri_instance.xpath("//#{namespace}#{root_node}/title").text
|
10
|
+
feed_rss.url = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}link[@rel='self']").attr("href").value
|
11
|
+
feed_rss.description = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}subtitle").text.strip
|
12
|
+
feed_rss.updated_at = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}updated").text
|
13
|
+
nokogiri_instance.xpath("//#{namespace}entry").each {|item| feed_rss.entries << Urss::Feed::Atom::Entry.build(item, namespace)}
|
14
|
+
|
15
|
+
feed_rss
|
16
|
+
end
|
17
|
+
|
18
|
+
# ~~~~ Instance methods ~~~~
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Urss::Feed::Atom::Entry < Urss::Feed::Entry
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
|
5
|
+
# ~~~~ Class methods ~~~~
|
6
|
+
def self.build(nokogiri_instance, namespace=nil)
|
7
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::Element)
|
8
|
+
|
9
|
+
entry = self.new
|
10
|
+
entry.title = nokogiri_instance.xpath("./#{namespace}title").text
|
11
|
+
entry.url = nokogiri_instance.xpath("./#{namespace}link[@rel='alternate']").attr("href").value
|
12
|
+
entry.created_at = nokogiri_instance.xpath("./#{namespace}published").text
|
13
|
+
entry.author = nokogiri_instance.xpath("./#{namespace}author/#{namespace}name").text
|
14
|
+
entry.content = nokogiri_instance.xpath("./description").text
|
15
|
+
|
16
|
+
begin
|
17
|
+
# When having only one media:content then all media:* nodes are used to create one Urss::Media
|
18
|
+
# Otherwise each media:* are different Urss::Media
|
19
|
+
single_media = nokogiri_instance.xpath("./media:content").size == 1
|
20
|
+
media = nil
|
21
|
+
nokogiri_instance.xpath("./media:*").each do |media_attributes|
|
22
|
+
if single_media
|
23
|
+
media = Urss::Media.new if media.nil?
|
24
|
+
media.update(media_attributes)
|
25
|
+
else
|
26
|
+
media = Urss::Media.new
|
27
|
+
media.update(media_attributes)
|
28
|
+
media_attributes.children.select{|child| child.class == Nokogiri::XML::Element}.each do |element|
|
29
|
+
media.update(element)
|
30
|
+
end
|
31
|
+
entry.medias << media
|
32
|
+
end
|
33
|
+
end
|
34
|
+
entry.medias << media if single_media
|
35
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
36
|
+
# No media element
|
37
|
+
end
|
38
|
+
|
39
|
+
entry
|
40
|
+
end
|
41
|
+
|
42
|
+
# ~~~~ Instance methods ~~~~
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Urss::Feed::Entry
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
attr_accessor :title, :url, :comments_url, :created_at, :author, :categories, :content, :medias
|
5
|
+
|
6
|
+
# ~~~~ Class methods ~~~~
|
7
|
+
|
8
|
+
# ~~~~ Instance methods ~~~~
|
9
|
+
def initialize
|
10
|
+
self.title = nil
|
11
|
+
self.url = nil
|
12
|
+
self.comments_url = nil
|
13
|
+
self.created_at = nil
|
14
|
+
self.author = nil
|
15
|
+
self.categories = []
|
16
|
+
self.content = nil
|
17
|
+
self.medias = []
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Urss::Feed::Rss < Urss::Feed
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
|
5
|
+
# ~~~~ Class methods ~~~~
|
6
|
+
def self.build(nokogiri_instance, namespace, root_node)
|
7
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::NodeSet)
|
8
|
+
feed_rss = self.new
|
9
|
+
feed_rss.title = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}title").text
|
10
|
+
feed_rss.url = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}link").text
|
11
|
+
feed_rss.description = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}description").text
|
12
|
+
feed_rss.updated_at = nokogiri_instance.xpath("//#{namespace}#{root_node}/#{namespace}pubDate").text
|
13
|
+
if feed_rss.updated_at.nil? || feed_rss.updated_at.empty?
|
14
|
+
begin
|
15
|
+
feed_rss.updated_at = nokogiri_instance.xpath("//#{namespace}#{root_node}/dc:date").text
|
16
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
17
|
+
# No pubDate or date field
|
18
|
+
end
|
19
|
+
end
|
20
|
+
nokogiri_instance.xpath("//#{namespace}item").each {|item| feed_rss.entries << Urss::Feed::Rss::Entry.build(item, namespace)}
|
21
|
+
|
22
|
+
feed_rss
|
23
|
+
end
|
24
|
+
|
25
|
+
# ~~~~ Instance methods ~~~~
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Urss::Feed::Rss::Entry < Urss::Feed::Entry
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
|
5
|
+
# ~~~~ Class methods ~~~~
|
6
|
+
def self.build(nokogiri_instance, namespace=nil)
|
7
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::Element)
|
8
|
+
|
9
|
+
entry = self.new
|
10
|
+
entry.title = nokogiri_instance.xpath("./#{namespace}title").text
|
11
|
+
entry.url = nokogiri_instance.xpath("./#{namespace}link").text
|
12
|
+
entry.comments_url = nokogiri_instance.xpath("./comments").text
|
13
|
+
entry.created_at = nokogiri_instance.xpath("./pubDate").text
|
14
|
+
if entry.created_at.nil? || entry.created_at.empty?
|
15
|
+
entry.created_at = nokogiri_instance.xpath("./dc:date").text
|
16
|
+
end
|
17
|
+
entry.author = nokogiri_instance.xpath("./dc:creator", nokogiri_instance.namespaces).text
|
18
|
+
entry.categories = nokogiri_instance.search("category").collect(&:text).join(", ")
|
19
|
+
entry.content = nokogiri_instance.xpath("./description").text
|
20
|
+
|
21
|
+
begin
|
22
|
+
# When having only one media:content then all media:* nodes are used to create one Urss::Media
|
23
|
+
# Otherwise each media:* are different Urss::Media
|
24
|
+
single_media = nokogiri_instance.xpath("./media:content").size == 1
|
25
|
+
media = nil
|
26
|
+
nokogiri_instance.xpath("./media:*").each do |media_attributes|
|
27
|
+
if single_media
|
28
|
+
media = Urss::Media.new if media.nil?
|
29
|
+
media.update(media_attributes)
|
30
|
+
else
|
31
|
+
media = Urss::Media.new
|
32
|
+
media.update(media_attributes)
|
33
|
+
media_attributes.children.select{|child| child.class == Nokogiri::XML::Element}.each do |element|
|
34
|
+
media.update(element)
|
35
|
+
end
|
36
|
+
entry.medias << media
|
37
|
+
end
|
38
|
+
end
|
39
|
+
entry.medias << media if single_media
|
40
|
+
rescue Nokogiri::XML::XPath::SyntaxError
|
41
|
+
# No media element
|
42
|
+
end
|
43
|
+
|
44
|
+
entry
|
45
|
+
end
|
46
|
+
|
47
|
+
# ~~~~ Instance methods ~~~~
|
48
|
+
|
49
|
+
end
|
data/lib/urss/media.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
class Urss::Media
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
attr_accessor :content_url, :title, :thumbnail_url
|
5
|
+
|
6
|
+
# ~~~~ Class methods ~~~~
|
7
|
+
|
8
|
+
# ~~~~ Instance methods ~~~~
|
9
|
+
def initialize
|
10
|
+
self.content_url = nil
|
11
|
+
self.title = nil
|
12
|
+
self.thumbnail_url = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(nokogiri_instance)
|
16
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::Element)
|
17
|
+
|
18
|
+
if nokogiri_instance.attributes["url"]
|
19
|
+
if nokogiri_instance.name == "thumbnail"
|
20
|
+
self.thumbnail_url = nokogiri_instance.attributes["url"].value
|
21
|
+
else
|
22
|
+
self.content_url = nokogiri_instance.attributes["url"].value
|
23
|
+
end
|
24
|
+
else
|
25
|
+
if nokogiri_instance.name == "title"
|
26
|
+
self.title = nokogiri_instance.text
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/urss/rss.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Urss::Rss
|
2
|
+
|
3
|
+
# ~~~~ Attributes ~~~~
|
4
|
+
|
5
|
+
# ~~~~ Class methods ~~~~
|
6
|
+
def self.build(nokogiri_instance)
|
7
|
+
raise Urss::NotANokogiriInstance unless nokogiri_instance.is_a?(Nokogiri::XML::Document)
|
8
|
+
|
9
|
+
namespace = nokogiri_instance.namespaces["xmlns"] ? "xmlns:" : nil
|
10
|
+
|
11
|
+
# Factory
|
12
|
+
["channel", "feed"].each do |root|
|
13
|
+
unless (root_instance = nokogiri_instance.xpath("//#{namespace}#{root}")).empty?
|
14
|
+
rss_object = case root
|
15
|
+
when "channel"
|
16
|
+
Urss::Feed::Rss
|
17
|
+
when "feed"
|
18
|
+
Urss::Feed::Atom
|
19
|
+
end.build(root_instance, namespace, root)
|
20
|
+
return rss_object
|
21
|
+
break
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# ~~~~ Instance methods ~~~~
|
27
|
+
|
28
|
+
end
|
data/lib/urss/version.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
SimpleCov.start
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require "bundler/setup"
|
6
|
+
|
7
|
+
# require "webmock/rspec" is not working. So:
|
8
|
+
require "webmock"
|
9
|
+
include WebMock::API
|
10
|
+
|
11
|
+
require "support/webmocks"
|
12
|
+
|
13
|
+
require "urss"
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<feed xmlns="http://www.w3.org/2005/Atom">
|
3
|
+
<rss:title type="text">dive into mark</title>
|
4
|
+
<subtitle type="html">
|
5
|
+
A <em>lot</em> of effort
|
6
|
+
went into making this effortless
|
7
|
+
</subtitle>
|
8
|
+
<updated>2005-07-31T12:29:29Z</updated>
|
9
|
+
<id>tag:example.org,2003:3</id>
|
10
|
+
<link rel="alternate" type="text/html"
|
11
|
+
hreflang="en" href="http://example.org/"/>
|
12
|
+
<link rel="self" type="application/atom+xml"
|
13
|
+
href="http://example.org/feed.atom"/>
|
14
|
+
<rights>Copyright (c) 2003, Mark Pilgrim</rights>
|
15
|
+
<generator uri="http://www.example.com/" version="1.0">
|
16
|
+
Example Toolkit
|
17
|
+
</generator>
|
18
|
+
<entry>
|
19
|
+
<title>Atom draft-07 snapshot</title>
|
20
|
+
<link rel="alternate" type="text/html"
|
21
|
+
href="http://example.org/2005/04/02/atom"/>
|
22
|
+
<link rel="enclosure" type="audio/mpeg" length="1337"
|
23
|
+
href="http://example.org/audio/ph34r_my_podcast.mp3"/>
|
24
|
+
<id>tag:example.org,2003:3.2397</id>
|
25
|
+
<updated>2005-07-31T12:29:29Z</updated>
|
26
|
+
<published>2003-12-13T08:29:29-04:00</published>
|
27
|
+
<author>
|
28
|
+
<name>Mark Pilgrim</name>
|
29
|
+
<uri>http://example.org/</uri>
|
30
|
+
<email>f8dy@example.com</email>
|
31
|
+
</author>
|
32
|
+
<contributor>
|
33
|
+
<name>Sam Ruby</name>
|
34
|
+
</contributor>
|
35
|
+
<contributor>
|
36
|
+
<name>Joe Gregorio</name>
|
37
|
+
</contributor>
|
38
|
+
<content type="xhtml" xml:lang="en"
|
39
|
+
xml:base="http://diveintomark.org/">
|
40
|
+
<div xmlns="http://www.w3.org/1999/xhtml">
|
41
|
+
<p><i>[Update: The Atom draft is finished.]</i></p>
|
42
|
+
</div>
|
43
|
+
</content>
|
44
|
+
</entry>
|
45
|
+
</feed>
|