libxml-feed 0.0.1
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/lib/xml/libxml/feed.rb +15 -0
- data/lib/xml/libxml/feed/builder.rb +10 -0
- data/lib/xml/libxml/feed/builders/rss.rb +144 -0
- data/lib/xml/libxml/feed/parser.rb +87 -0
- data/lib/xml/libxml/feed/parsers/rss.rb +113 -0
- data/lib/xml/libxml/feed/parsers/rss/channel.rb +14 -0
- data/lib/xml/libxml/feed/parsers/rss/common.rb +47 -0
- data/lib/xml/libxml/feed/parsers/rss/item.rb +5 -0
- data/test/test_builder_interface.rb +127 -0
- data/test/test_parser_interface.rb +71 -0
- data/test/test_rss_parser.rb +165 -0
- metadata +71 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
class XML::Feed::Builder::Rss
|
2
|
+
class Channels
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :doc
|
6
|
+
|
7
|
+
def initialize(doc)
|
8
|
+
@doc = doc
|
9
|
+
end
|
10
|
+
|
11
|
+
def create
|
12
|
+
node = Channel.new(@doc)
|
13
|
+
yield node
|
14
|
+
@doc.root << node.node
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
@doc.root.find('/rss/channel').each do |x|
|
19
|
+
yield Channel.new(@doc, x)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def [](x)
|
24
|
+
return Channel.new(@doc, @doc.root.find('/rss/channel').to_a[x])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Channel
|
29
|
+
attr_reader :doc
|
30
|
+
attr_reader :node
|
31
|
+
|
32
|
+
def initialize(doc, node=nil)
|
33
|
+
@doc = doc
|
34
|
+
@node = node
|
35
|
+
@node = XML::Node.new("channel") unless @node
|
36
|
+
end
|
37
|
+
|
38
|
+
def items
|
39
|
+
return Items.new(@doc, @node)
|
40
|
+
end
|
41
|
+
|
42
|
+
alias item items
|
43
|
+
end
|
44
|
+
|
45
|
+
class Items
|
46
|
+
include Enumerable
|
47
|
+
|
48
|
+
def initialize(doc, channel)
|
49
|
+
@doc = doc
|
50
|
+
@channel = channel
|
51
|
+
end
|
52
|
+
|
53
|
+
def create
|
54
|
+
node = Item.new(@channel)
|
55
|
+
yield node
|
56
|
+
@channel << node.node
|
57
|
+
end
|
58
|
+
|
59
|
+
def each
|
60
|
+
@channel.find('./item').each do |x|
|
61
|
+
yield Item.new(@channel, x)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def [](x)
|
66
|
+
return Item.new(@channel, @channel.find('./item').to_a[x])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Item
|
71
|
+
attr_reader :channel
|
72
|
+
attr_reader :node
|
73
|
+
|
74
|
+
def initialize(channel, node=nil)
|
75
|
+
@channel = channel
|
76
|
+
@node = node
|
77
|
+
|
78
|
+
@node = XML::Node.new('item') unless @node
|
79
|
+
@params = Hash.new
|
80
|
+
end
|
81
|
+
|
82
|
+
# basic parameters that just get enclosed by a <tag>. More involved
|
83
|
+
# stuff should create it's own pair of methods.
|
84
|
+
def method_missing(method, *args)
|
85
|
+
meth = method.to_s
|
86
|
+
if meth =~ /=$/ # assignment
|
87
|
+
my_meth = meth.sub(/=$/, '')
|
88
|
+
|
89
|
+
node = XML::Node.new(my_meth)
|
90
|
+
node.content = args[0].to_s
|
91
|
+
@node << node
|
92
|
+
return node
|
93
|
+
end
|
94
|
+
|
95
|
+
# if we're reading, we want to collect each node that matches our method name.
|
96
|
+
# the problem is, XML::XPath::Object#find doesn't work on detached nodes, which ours
|
97
|
+
# is at this state. So we have to search manually.
|
98
|
+
|
99
|
+
array = []
|
100
|
+
|
101
|
+
@node.each do |x|
|
102
|
+
if x.name == meth
|
103
|
+
array.push x
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
return array.length > 1 ? array : array[0]
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class XML::Feed::Builder::Rss
|
113
|
+
|
114
|
+
attr_accessor :version
|
115
|
+
attr_accessor :doc
|
116
|
+
|
117
|
+
def initialize(version, parser=nil)
|
118
|
+
@version = version
|
119
|
+
@parser = parser
|
120
|
+
|
121
|
+
build_document
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_xml
|
125
|
+
@doc.to_s
|
126
|
+
end
|
127
|
+
|
128
|
+
def channel
|
129
|
+
return Channels.new(@doc)
|
130
|
+
end
|
131
|
+
|
132
|
+
protected
|
133
|
+
|
134
|
+
def build_document
|
135
|
+
if @parser
|
136
|
+
@doc = @parser.doc
|
137
|
+
else
|
138
|
+
@doc = XML::Document.new
|
139
|
+
@doc.root = XML::Node.new("rss")
|
140
|
+
end
|
141
|
+
|
142
|
+
@doc.root["version"] = @version
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module XML
|
2
|
+
module Feed
|
3
|
+
module Parser
|
4
|
+
def self.method_missing(method, *args)
|
5
|
+
require "xml/libxml/feed/parsers/#{method.to_s.downcase}"
|
6
|
+
return self.const_get(method.to_s.capitalize).new(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
class InvalidHandle < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
class ValidationError < Exception
|
13
|
+
end
|
14
|
+
|
15
|
+
class Tag
|
16
|
+
attr_reader :property
|
17
|
+
attr_reader :data
|
18
|
+
|
19
|
+
def initialize(name, data, properties)
|
20
|
+
if properties
|
21
|
+
@property = properties.to_h.dup
|
22
|
+
else
|
23
|
+
@property = { }
|
24
|
+
end
|
25
|
+
|
26
|
+
if data
|
27
|
+
@data = data
|
28
|
+
else
|
29
|
+
@data = ""
|
30
|
+
end
|
31
|
+
|
32
|
+
@name = name.dup
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
@data.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
alias to_str to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
class Base
|
43
|
+
attr_reader :xml
|
44
|
+
attr_reader :version
|
45
|
+
attr_reader :validated
|
46
|
+
attr_reader :doc
|
47
|
+
|
48
|
+
def initialize(version, io, auto_validate=true)
|
49
|
+
@version = version
|
50
|
+
|
51
|
+
case io
|
52
|
+
when IO
|
53
|
+
@xml = io.read
|
54
|
+
io.close
|
55
|
+
when StringIO
|
56
|
+
@xml = io.read
|
57
|
+
io.close
|
58
|
+
when String
|
59
|
+
@xml = io.dup
|
60
|
+
else
|
61
|
+
raise XML::Feed::Parser::InvalidHandle, "Must be IO, StringIO, or String object"
|
62
|
+
end
|
63
|
+
|
64
|
+
@xml.freeze
|
65
|
+
@version.freeze
|
66
|
+
@validated = false
|
67
|
+
|
68
|
+
if @xml.length == 0
|
69
|
+
raise XML::Feed::Parser::InvalidHandle, "Resulting XML String must have a length greater than 0"
|
70
|
+
end
|
71
|
+
|
72
|
+
@doc = XML::Parser.string(@xml).parse
|
73
|
+
|
74
|
+
if auto_validate
|
75
|
+
validate!
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def validation_error(error_string)
|
82
|
+
raise ValidationError, error_string
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module XML
|
2
|
+
module Feed
|
3
|
+
module Parser
|
4
|
+
class Rss < XML::Feed::Parser::Base
|
5
|
+
require 'xml/libxml/feed/parsers/rss/channel'
|
6
|
+
require 'xml/libxml/feed/parsers/rss/item'
|
7
|
+
|
8
|
+
def initialize(version, io, auto_validate=true)
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def validate!
|
13
|
+
case @version
|
14
|
+
when '2.0'
|
15
|
+
validate_20
|
16
|
+
else
|
17
|
+
validation_error "Validating RSS version #{@version} is not supported (yet!)"
|
18
|
+
end
|
19
|
+
|
20
|
+
@validated = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def channel
|
24
|
+
channels = []
|
25
|
+
@doc.find('/rss/channel').each do |x|
|
26
|
+
channels.push(Channel.new(x))
|
27
|
+
end
|
28
|
+
|
29
|
+
return channels
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
#
|
35
|
+
# Given a node, an xpath expression to apply to that node, the
|
36
|
+
# error_message is thrown via validation_error() if the block
|
37
|
+
# evaluates to a false value or it raises an exception itself.
|
38
|
+
#
|
39
|
+
def validate_xpath(node, xpath, error_message, &block)
|
40
|
+
node = node.find(xpath)
|
41
|
+
|
42
|
+
result = true
|
43
|
+
|
44
|
+
if node and node.first
|
45
|
+
begin
|
46
|
+
result = yield node.first.content
|
47
|
+
rescue Exception => e
|
48
|
+
validation_error error_message
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
validation_error error_message unless result
|
53
|
+
end
|
54
|
+
|
55
|
+
# all the validation rules for RSS 2.0 that are fit to print.
|
56
|
+
def validate_20
|
57
|
+
validation_error "Document Root is not named 'rss'" unless @doc.root.name.downcase == "rss"
|
58
|
+
validation_error "Document Version does not exist" unless @doc.root["version"]
|
59
|
+
validation_error "Document Version is not '2.0'" unless @doc.root["version"] == "2.0"
|
60
|
+
validation_error "Channel elements do not exist" if @doc.find('/rss/channel').length == 0
|
61
|
+
|
62
|
+
@doc.find('/rss/channel').each_with_index do |node, i|
|
63
|
+
|
64
|
+
validate_20_channel(node, i)
|
65
|
+
|
66
|
+
if items = node.find('./item')
|
67
|
+
items.each_with_index do |item, item_i|
|
68
|
+
validation_error "Item must contain a title or description" unless item.find('./title').first or item.find('./description').first
|
69
|
+
validate_xpath(item, './pubDate', "Channel #{i} item #{item_i} element pubDate does not parse as a rfc822 time") do |x|
|
70
|
+
Time.rfc2822(x)
|
71
|
+
end
|
72
|
+
validate_xpath(item, './link', "Channel #{i} item #{item_i} element link does not parse as a URI") do |x|
|
73
|
+
URI.parse(x)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_20_channel(node, i)
|
81
|
+
%w(title link description).each do |x|
|
82
|
+
validation_error "Channel node #{i} does not have a #{x} element" unless node.find('./' + x).length > 0
|
83
|
+
validation_error "Channel node #{i} has more than one #{x} element" if node.find('./' + x).length > 1
|
84
|
+
end
|
85
|
+
%w(link docs).each do |uri|
|
86
|
+
validate_xpath(node, "./#{uri}", "Channel #{i} element #{uri} does not parse as a URI") do |x|
|
87
|
+
URI.parse(x)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
%w(pubDate lastBuildDate).each do |t|
|
91
|
+
validate_xpath(node, "./#{t}", "Channel #{i} element #{t} does not parse as a rfc822 time") do |x|
|
92
|
+
Time.rfc2822(x)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
validate_xpath(node, './ttl', 'Channel #{i} element ttl is not an integer') do |x|
|
96
|
+
x.to_i.to_s == x
|
97
|
+
end
|
98
|
+
if node.find('./cloud')
|
99
|
+
#warn "Cloud support is currently not implemented"
|
100
|
+
end
|
101
|
+
if image = node.find('./image')
|
102
|
+
%w(url link).each do |ele|
|
103
|
+
validate_xpath(node, "./image/#{ele}", "Channel #{i} element image/#{ele} does not parse as a URI") do |x|
|
104
|
+
URI.parse(x)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
validation_error "Title element must exist in Channel #{i} element image" unless node.find('./title').length > 0
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'xml/libxml/feed/parsers/rss/common'
|
2
|
+
|
3
|
+
class XML::Feed::Parser::Rss::Channel
|
4
|
+
include XML::Feed::Parser::Rss::Common
|
5
|
+
|
6
|
+
def item
|
7
|
+
items = []
|
8
|
+
@node.find('./item').each do |x|
|
9
|
+
items.push XML::Feed::Parser::Rss::Item.new(x)
|
10
|
+
end
|
11
|
+
|
12
|
+
return items
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module XML::Feed::Parser::Rss::Common
|
2
|
+
attr_reader :node
|
3
|
+
|
4
|
+
def initialize(node)
|
5
|
+
@node = node
|
6
|
+
end
|
7
|
+
|
8
|
+
def method_missing(methId, *args)
|
9
|
+
requested = @node.find("./#{methId.to_s}")
|
10
|
+
if requested.first
|
11
|
+
if requested.length > 1
|
12
|
+
array = []
|
13
|
+
requested.each do |x|
|
14
|
+
array.push XML::Feed::Parser::Tag.new(x.name, type_node(x), x.properties)
|
15
|
+
end
|
16
|
+
|
17
|
+
return array
|
18
|
+
else
|
19
|
+
r = requested.first
|
20
|
+
return XML::Feed::Parser::Tag.new(r.name, type_node(r), r.properties)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
return nil
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def type_node(node)
|
30
|
+
case node.name
|
31
|
+
when 'pubDate'
|
32
|
+
Time.rfc2822(node.content)
|
33
|
+
when 'lastBuildDate'
|
34
|
+
Time.rfc2822(node.content)
|
35
|
+
when 'docs'
|
36
|
+
URI.parse(node.content)
|
37
|
+
when 'link'
|
38
|
+
URI.parse(node.content)
|
39
|
+
when 'url'
|
40
|
+
URI.parse(node.content)
|
41
|
+
when 'ttl'
|
42
|
+
node.content.to_i
|
43
|
+
else
|
44
|
+
node.content
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'xml/libxml/feed'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
class TestBuilderInterface < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_loader
|
8
|
+
builder = nil
|
9
|
+
assert_nothing_raised do
|
10
|
+
builder = XML::Feed::Builder.rss('2.0')
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_instance_of(XML::Feed::Builder::Rss, builder)
|
14
|
+
|
15
|
+
# test with a parser
|
16
|
+
parser = nil
|
17
|
+
assert_nothing_raised do
|
18
|
+
parser = XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_instance_of(XML::Feed::Parser::Rss, parser)
|
22
|
+
|
23
|
+
assert_nothing_raised do
|
24
|
+
builder = XML::Feed::Builder.rss('2.0', parser)
|
25
|
+
end
|
26
|
+
|
27
|
+
assert_instance_of(XML::Feed::Builder::Rss, builder)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_output
|
31
|
+
builder = nil
|
32
|
+
|
33
|
+
assert_nothing_raised do
|
34
|
+
builder = XML::Feed::Builder.rss("2.0")
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_equal("<?xml version=\"1.0\"?>\n<rss version=\"2.0\"/>\n", builder.to_xml)
|
38
|
+
|
39
|
+
parser = nil
|
40
|
+
|
41
|
+
#
|
42
|
+
# Tests that the builder and parser interoperate.
|
43
|
+
#
|
44
|
+
|
45
|
+
assert_nothing_raised do
|
46
|
+
parser = XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_nothing_raised do
|
50
|
+
builder = XML::Feed::Builder.rss("2.0", parser)
|
51
|
+
end
|
52
|
+
|
53
|
+
assert_equal(parser.doc.to_s, builder.to_xml)
|
54
|
+
|
55
|
+
#
|
56
|
+
# Tests that we can build a basic set of rss data.
|
57
|
+
#
|
58
|
+
|
59
|
+
assert_nothing_raised do
|
60
|
+
builder = XML::Feed::Builder.rss("2.0")
|
61
|
+
end
|
62
|
+
|
63
|
+
assert_nothing_raised do
|
64
|
+
builder.channel.create do |x|
|
65
|
+
x.items.create do |y|
|
66
|
+
y.title = "Bar"
|
67
|
+
y.description = "Foo"
|
68
|
+
y.link = URI.parse("http://funky.cold.medina.com")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
assert_equal(
|
74
|
+
"<?xml version=\"1.0\"?>\n<rss version=\"2.0\">\n <channel>\n <item>\n <title>Bar</title>\n <description>Foo</description>\n <link>http://funky.cold.medina.com</link>\n </item>\n </channel>\n</rss>\n",
|
75
|
+
builder.to_xml
|
76
|
+
)
|
77
|
+
|
78
|
+
#
|
79
|
+
# Tests that we can create a basic set of RSS data in an item and append it to parsed data.
|
80
|
+
#
|
81
|
+
|
82
|
+
assert_nothing_raised do
|
83
|
+
builder = XML::Feed::Builder.rss("2.0", parser)
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_nothing_raised do
|
87
|
+
builder.channel[0].items.create do |y|
|
88
|
+
y.title = "Bar"
|
89
|
+
y.description = "Foo"
|
90
|
+
y.link = URI.parse("http://funky.cold.medina.com")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
assert_equal(File.open("test/data/valid-2.0-rss-build1.xml").read.gsub(/\r/, ''), builder.to_xml)
|
95
|
+
|
96
|
+
#
|
97
|
+
# Tests that we can create elements in an item that have properties.
|
98
|
+
#
|
99
|
+
|
100
|
+
assert_nothing_raised do
|
101
|
+
parser = XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
102
|
+
builder = XML::Feed::Builder.rss("2.0", parser)
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_nothing_raised do
|
106
|
+
builder.channel[0].items.create do |y|
|
107
|
+
y.title = "Bar"
|
108
|
+
y.description = "Foo"
|
109
|
+
y.link = URI.parse("http://funky.cold.medina.com")
|
110
|
+
y.category = "my_category"
|
111
|
+
y.category["domain"] = "alt.swedish.chef.bork.bork.bork"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
assert_equal(File.open("test/data/valid-2.0-rss-build2.xml").read.gsub(/\r/, ''), builder.to_xml)
|
116
|
+
|
117
|
+
# tests two things:
|
118
|
+
# 1) title only returns one object, and therefore isn't boxed
|
119
|
+
# 2) tests that we get data that is parsed as well as created
|
120
|
+
|
121
|
+
assert_kind_of(XML::Node, builder.channel[0].item[0].title)
|
122
|
+
assert_equal(builder.channel[0].item[0].title.content, "Seventh Heaven! Ryan Hurls Another No Hitter")
|
123
|
+
|
124
|
+
assert_kind_of(XML::Node, builder.channel[0].item[-1].title)
|
125
|
+
assert_equal(builder.channel[0].item[-1].title.content, "Bar")
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'xml/libxml/feed'
|
3
|
+
require 'stringio'
|
4
|
+
|
5
|
+
class TestParserInterface < Test::Unit::TestCase
|
6
|
+
def test_loader
|
7
|
+
parser = nil
|
8
|
+
assert_nothing_raised do
|
9
|
+
parser = XML::Feed::Parser.rss('2.0', "<rss></rss>", false)
|
10
|
+
end
|
11
|
+
|
12
|
+
assert_instance_of(XML::Feed::Parser::Rss, parser)
|
13
|
+
end
|
14
|
+
|
15
|
+
# tests various forms of IO that can be passed to the constructors for the default parsers
|
16
|
+
def test_IO_acceptance
|
17
|
+
assert_nothing_raised do
|
18
|
+
XML::Feed::Parser.rss('2.0', '<rss></rss>', false)
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_nothing_raised do
|
22
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/basic.xml', 'r'), false)
|
23
|
+
end
|
24
|
+
|
25
|
+
assert_nothing_raised do
|
26
|
+
XML::Feed::Parser.rss('2.0', StringIO.new("<rss></rss>"), false)
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_raise(XML::Feed::Parser::InvalidHandle) do
|
30
|
+
XML::Feed::Parser.rss('2.0', Array.new, false)
|
31
|
+
end
|
32
|
+
|
33
|
+
assert_raise(XML::Feed::Parser::InvalidHandle) do
|
34
|
+
XML::Feed::Parser.rss('2.0', '', false)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_base_accessors
|
39
|
+
parser = nil
|
40
|
+
|
41
|
+
assert_nothing_raised do
|
42
|
+
parser = XML::Feed::Parser.rss('2.0', '<rss></rss>', false)
|
43
|
+
end
|
44
|
+
|
45
|
+
assert_equal('<rss></rss>', parser.xml)
|
46
|
+
assert_equal('2.0', parser.version)
|
47
|
+
|
48
|
+
assert_nothing_raised do
|
49
|
+
parser = XML::Feed::Parser.rss('1.0', File.open('test/data/basic.xml', 'r'), false)
|
50
|
+
end
|
51
|
+
|
52
|
+
assert_equal(File.open('test/data/basic.xml').read, parser.xml)
|
53
|
+
assert_equal('1.0', parser.version)
|
54
|
+
|
55
|
+
assert_nothing_raised do
|
56
|
+
parser = XML::Feed::Parser.rss('2.0', StringIO.new('<rss></rss>'), false)
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_equal('<rss></rss>', parser.xml)
|
60
|
+
assert_equal('2.0', parser.version)
|
61
|
+
assert(!parser.validated)
|
62
|
+
|
63
|
+
assert_nothing_raised do
|
64
|
+
parser = XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml', 'r'), true)
|
65
|
+
end
|
66
|
+
|
67
|
+
assert_equal(File.open('test/data/valid-2.0-rss.xml').read, parser.xml)
|
68
|
+
assert_equal('2.0', parser.version)
|
69
|
+
assert(parser.validated)
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'xml/libxml/feed'
|
3
|
+
|
4
|
+
class TestRssParser < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_rss_interface
|
7
|
+
parser = nil
|
8
|
+
|
9
|
+
assert_nothing_raised do
|
10
|
+
parser = XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
11
|
+
end
|
12
|
+
|
13
|
+
assert(parser.channel)
|
14
|
+
assert_kind_of(Array, parser.channel)
|
15
|
+
assert_kind_of(XML::Feed::Parser::Rss::Channel, parser.channel[0])
|
16
|
+
|
17
|
+
assert(parser.channel[0].pubDate)
|
18
|
+
assert_kind_of(XML::Feed::Parser::Tag, parser.channel[0].pubDate)
|
19
|
+
assert_kind_of(Time, parser.channel[0].pubDate.data)
|
20
|
+
assert_kind_of(Hash, parser.channel[0].pubDate.property)
|
21
|
+
|
22
|
+
assert(parser.channel[0].lastBuildDate)
|
23
|
+
assert_kind_of(XML::Feed::Parser::Tag, parser.channel[0].lastBuildDate)
|
24
|
+
assert_kind_of(Time, parser.channel[0].lastBuildDate.data)
|
25
|
+
assert_kind_of(Hash, parser.channel[0].lastBuildDate.property)
|
26
|
+
|
27
|
+
assert(parser.channel[0].link)
|
28
|
+
assert_kind_of(XML::Feed::Parser::Tag, parser.channel[0].link)
|
29
|
+
assert_kind_of(URI, parser.channel[0].link.data)
|
30
|
+
assert_kind_of(Hash, parser.channel[0].link.property)
|
31
|
+
|
32
|
+
assert(parser.channel[0].docs)
|
33
|
+
assert_kind_of(XML::Feed::Parser::Tag, parser.channel[0].docs)
|
34
|
+
assert_kind_of(URI, parser.channel[0].docs.data)
|
35
|
+
assert_kind_of(Hash, parser.channel[0].docs.property)
|
36
|
+
|
37
|
+
assert(parser.channel[0].ttl)
|
38
|
+
assert_kind_of(XML::Feed::Parser::Tag, parser.channel[0].ttl)
|
39
|
+
assert_kind_of(Numeric, parser.channel[0].ttl.data)
|
40
|
+
assert_kind_of(Hash, parser.channel[0].ttl.property)
|
41
|
+
|
42
|
+
assert(parser.channel[0].item)
|
43
|
+
assert_kind_of(Array, parser.channel[0].item)
|
44
|
+
|
45
|
+
parser.channel[0].item.each do |item|
|
46
|
+
assert_kind_of(XML::Feed::Parser::Rss::Item, item)
|
47
|
+
if item.link
|
48
|
+
assert_kind_of(XML::Feed::Parser::Tag, item.link)
|
49
|
+
assert_nothing_raised do
|
50
|
+
"string" + item.link
|
51
|
+
end
|
52
|
+
assert_kind_of(URI, item.link.data)
|
53
|
+
assert_kind_of(Hash, item.link.property)
|
54
|
+
end
|
55
|
+
if item.description
|
56
|
+
assert_kind_of(XML::Feed::Parser::Tag, item.description)
|
57
|
+
assert_nothing_raised do
|
58
|
+
"string" + item.description
|
59
|
+
end
|
60
|
+
assert_kind_of(String, item.description.data)
|
61
|
+
assert_kind_of(Hash, item.description.property)
|
62
|
+
end
|
63
|
+
if item.title
|
64
|
+
assert_kind_of(XML::Feed::Parser::Tag, item.title)
|
65
|
+
assert_nothing_raised do
|
66
|
+
"string" + item.title
|
67
|
+
end
|
68
|
+
assert_kind_of(String, item.title.data)
|
69
|
+
assert_kind_of(Hash, item.title.property)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_validate_rss
|
75
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
76
|
+
XML::Feed::Parser.rss('1.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_validate_rss_20
|
81
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
82
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/basic.xml'), true)
|
83
|
+
end
|
84
|
+
|
85
|
+
# test a valid one first
|
86
|
+
assert_nothing_raised do
|
87
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/valid-2.0-rss.xml'), true)
|
88
|
+
end
|
89
|
+
|
90
|
+
# test without the version
|
91
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
92
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-root-2.0-rss.xml'), true)
|
93
|
+
end
|
94
|
+
|
95
|
+
# test without the version
|
96
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
97
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/missing-version-2.0-rss.xml'), true)
|
98
|
+
end
|
99
|
+
|
100
|
+
# invalid version
|
101
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
102
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/invalid-version-2.0-rss.xml'), true)
|
103
|
+
end
|
104
|
+
|
105
|
+
# missing channel
|
106
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
107
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/missing-channel-2.0-rss.xml'), true)
|
108
|
+
end
|
109
|
+
|
110
|
+
# missing channel title
|
111
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
112
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/missing-channel-title-2.0-rss.xml'), true)
|
113
|
+
end
|
114
|
+
|
115
|
+
# missing channel link
|
116
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
117
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/missing-channel-link-2.0-rss.xml'), true)
|
118
|
+
end
|
119
|
+
|
120
|
+
# missing channel description
|
121
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
122
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/missing-channel-description-2.0-rss.xml'), true)
|
123
|
+
end
|
124
|
+
|
125
|
+
# duplicate channel title
|
126
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
127
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/duplicate-channel-title-2.0-rss.xml'), true)
|
128
|
+
end
|
129
|
+
|
130
|
+
# duplicate channel link
|
131
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
132
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/duplicate-channel-link-2.0-rss.xml'), true)
|
133
|
+
end
|
134
|
+
|
135
|
+
# duplicate channel description
|
136
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
137
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/duplicate-channel-description-2.0-rss.xml'), true)
|
138
|
+
end
|
139
|
+
|
140
|
+
# bad URI
|
141
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
142
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-uri-2.0-rss.xml'), true)
|
143
|
+
end
|
144
|
+
|
145
|
+
# bad pubDate
|
146
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
147
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-pubdate-2.0-rss.xml'), true)
|
148
|
+
end
|
149
|
+
|
150
|
+
# bad ttl
|
151
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
152
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-ttl-2.0-rss.xml'), true)
|
153
|
+
end
|
154
|
+
|
155
|
+
# bad image
|
156
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
157
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-image-2.0-rss.xml'), true)
|
158
|
+
end
|
159
|
+
|
160
|
+
# bad item
|
161
|
+
assert_raise(XML::Feed::Parser::ValidationError) do
|
162
|
+
XML::Feed::Parser.rss('2.0', File.open('test/data/bad-item-2.0-rss.xml'), true)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: libxml-feed
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 0.0.1
|
7
|
+
date: 2008-01-05 00:00:00 -08:00
|
8
|
+
summary: Provides a alternative and faster feed (RSS, Atom, et al) parsing layer through libxml's parsing framework
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: erik@hollensbe.org
|
12
|
+
homepage:
|
13
|
+
rubyforge_project: libxml-tools
|
14
|
+
description:
|
15
|
+
autorequire:
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: true
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- Erik Hollensbe
|
31
|
+
files:
|
32
|
+
- lib/xml
|
33
|
+
- lib/xml/libxml
|
34
|
+
- lib/xml/libxml/feed
|
35
|
+
- lib/xml/libxml/feed/builder.rb
|
36
|
+
- lib/xml/libxml/feed/builders
|
37
|
+
- lib/xml/libxml/feed/builders/rss.rb
|
38
|
+
- lib/xml/libxml/feed/parser.rb
|
39
|
+
- lib/xml/libxml/feed/parsers
|
40
|
+
- lib/xml/libxml/feed/parsers/rss
|
41
|
+
- lib/xml/libxml/feed/parsers/rss/channel.rb
|
42
|
+
- lib/xml/libxml/feed/parsers/rss/common.rb
|
43
|
+
- lib/xml/libxml/feed/parsers/rss/item.rb
|
44
|
+
- lib/xml/libxml/feed/parsers/rss.rb
|
45
|
+
- lib/xml/libxml/feed.rb
|
46
|
+
- test/data
|
47
|
+
- test/test_builder_interface.rb
|
48
|
+
- test/test_parser_interface.rb
|
49
|
+
- test/test_rss_parser.rb
|
50
|
+
test_files: []
|
51
|
+
|
52
|
+
rdoc_options: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
executables: []
|
57
|
+
|
58
|
+
extensions: []
|
59
|
+
|
60
|
+
requirements: []
|
61
|
+
|
62
|
+
dependencies:
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: libxml-ruby
|
65
|
+
version_requirement:
|
66
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 0.0.0
|
71
|
+
version:
|