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