rubysl-rss 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE +25 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/lib/rss.rb +1 -0
- data/lib/rss/0.9.rb +428 -0
- data/lib/rss/1.0.rb +452 -0
- data/lib/rss/2.0.rb +111 -0
- data/lib/rss/atom.rb +749 -0
- data/lib/rss/content.rb +31 -0
- data/lib/rss/content/1.0.rb +10 -0
- data/lib/rss/content/2.0.rb +12 -0
- data/lib/rss/converter.rb +162 -0
- data/lib/rss/dublincore.rb +161 -0
- data/lib/rss/dublincore/1.0.rb +13 -0
- data/lib/rss/dublincore/2.0.rb +13 -0
- data/lib/rss/dublincore/atom.rb +17 -0
- data/lib/rss/image.rb +193 -0
- data/lib/rss/itunes.rb +410 -0
- data/lib/rss/maker.rb +44 -0
- data/lib/rss/maker/0.9.rb +467 -0
- data/lib/rss/maker/1.0.rb +434 -0
- data/lib/rss/maker/2.0.rb +223 -0
- data/lib/rss/maker/atom.rb +172 -0
- data/lib/rss/maker/base.rb +868 -0
- data/lib/rss/maker/content.rb +21 -0
- data/lib/rss/maker/dublincore.rb +124 -0
- data/lib/rss/maker/entry.rb +163 -0
- data/lib/rss/maker/feed.rb +429 -0
- data/lib/rss/maker/image.rb +111 -0
- data/lib/rss/maker/itunes.rb +242 -0
- data/lib/rss/maker/slash.rb +33 -0
- data/lib/rss/maker/syndication.rb +18 -0
- data/lib/rss/maker/taxonomy.rb +118 -0
- data/lib/rss/maker/trackback.rb +61 -0
- data/lib/rss/parser.rb +541 -0
- data/lib/rss/rexmlparser.rb +54 -0
- data/lib/rss/rss.rb +1312 -0
- data/lib/rss/slash.rb +49 -0
- data/lib/rss/syndication.rb +67 -0
- data/lib/rss/taxonomy.rb +145 -0
- data/lib/rss/trackback.rb +288 -0
- data/lib/rss/utils.rb +111 -0
- data/lib/rss/xml-stylesheet.rb +105 -0
- data/lib/rss/xml.rb +71 -0
- data/lib/rss/xmlparser.rb +93 -0
- data/lib/rss/xmlscanner.rb +121 -0
- data/lib/rubysl/rss.rb +2 -0
- data/lib/rubysl/rss/rss.rb +19 -0
- data/lib/rubysl/rss/version.rb +5 -0
- data/rubysl-rss.gemspec +23 -0
- metadata +153 -0
data/lib/rss/utils.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
module RSS
|
2
|
+
module Utils
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# Convert a name_with_underscores to CamelCase.
|
6
|
+
def to_class_name(name)
|
7
|
+
name.split(/[_\-]/).collect do |part|
|
8
|
+
"#{part[0, 1].upcase}#{part[1..-1]}"
|
9
|
+
end.join("")
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_file_and_line_from_caller(i=0)
|
13
|
+
file, line, = caller[i].split(':')
|
14
|
+
line = line.to_i
|
15
|
+
line += 1 if i.zero?
|
16
|
+
[file, line]
|
17
|
+
end
|
18
|
+
|
19
|
+
# escape '&', '"', '<' and '>' for use in HTML.
|
20
|
+
def html_escape(s)
|
21
|
+
s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<")
|
22
|
+
end
|
23
|
+
alias h html_escape
|
24
|
+
|
25
|
+
# If +value+ is an instance of class +klass+, return it, else
|
26
|
+
# create a new instance of +klass+ with value +value+.
|
27
|
+
def new_with_value_if_need(klass, value)
|
28
|
+
if value.is_a?(klass)
|
29
|
+
value
|
30
|
+
else
|
31
|
+
klass.new(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def element_initialize_arguments?(args)
|
36
|
+
[true, false].include?(args[0]) and args[1].is_a?(Hash)
|
37
|
+
end
|
38
|
+
|
39
|
+
module YesCleanOther
|
40
|
+
module_function
|
41
|
+
def parse(value)
|
42
|
+
if [true, false, nil].include?(value)
|
43
|
+
value
|
44
|
+
else
|
45
|
+
case value.to_s
|
46
|
+
when /\Ayes\z/i
|
47
|
+
true
|
48
|
+
when /\Aclean\z/i
|
49
|
+
false
|
50
|
+
else
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module YesOther
|
58
|
+
module_function
|
59
|
+
def parse(value)
|
60
|
+
if [true, false].include?(value)
|
61
|
+
value
|
62
|
+
else
|
63
|
+
/\Ayes\z/i.match(value.to_s) ? true : false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module CSV
|
69
|
+
module_function
|
70
|
+
def parse(value, &block)
|
71
|
+
if value.is_a?(String)
|
72
|
+
value = value.strip.split(/\s*,\s*/)
|
73
|
+
value = value.collect(&block) if block_given?
|
74
|
+
value
|
75
|
+
else
|
76
|
+
value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
module InheritedReader
|
82
|
+
def inherited_reader(constant_name)
|
83
|
+
base_class = inherited_base
|
84
|
+
result = base_class.const_get(constant_name)
|
85
|
+
found_base_class = false
|
86
|
+
ancestors.reverse_each do |klass|
|
87
|
+
if found_base_class
|
88
|
+
if klass.const_defined?(constant_name)
|
89
|
+
result = yield(result, klass.const_get(constant_name))
|
90
|
+
end
|
91
|
+
else
|
92
|
+
found_base_class = klass == base_class
|
93
|
+
end
|
94
|
+
end
|
95
|
+
result
|
96
|
+
end
|
97
|
+
|
98
|
+
def inherited_array_reader(constant_name)
|
99
|
+
inherited_reader(constant_name) do |result, current|
|
100
|
+
current + result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def inherited_hash_reader(constant_name)
|
105
|
+
inherited_reader(constant_name) do |result, current|
|
106
|
+
result.merge(current)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "rss/utils"
|
2
|
+
|
3
|
+
module RSS
|
4
|
+
|
5
|
+
module XMLStyleSheetMixin
|
6
|
+
attr_accessor :xml_stylesheets
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@xml_stylesheets = []
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def xml_stylesheet_pi
|
14
|
+
xsss = @xml_stylesheets.collect do |xss|
|
15
|
+
pi = xss.to_s
|
16
|
+
pi = nil if /\A\s*\z/ =~ pi
|
17
|
+
pi
|
18
|
+
end.compact
|
19
|
+
xsss.push("") unless xsss.empty?
|
20
|
+
xsss.join("\n")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class XMLStyleSheet
|
25
|
+
|
26
|
+
include Utils
|
27
|
+
|
28
|
+
ATTRIBUTES = %w(href type title media charset alternate)
|
29
|
+
|
30
|
+
GUESS_TABLE = {
|
31
|
+
"xsl" => "text/xsl",
|
32
|
+
"css" => "text/css",
|
33
|
+
}
|
34
|
+
|
35
|
+
attr_accessor(*ATTRIBUTES)
|
36
|
+
attr_accessor(:do_validate)
|
37
|
+
def initialize(*attrs)
|
38
|
+
if attrs.size == 1 and
|
39
|
+
(attrs.first.is_a?(Hash) or attrs.first.is_a?(Array))
|
40
|
+
attrs = attrs.first
|
41
|
+
end
|
42
|
+
@do_validate = true
|
43
|
+
ATTRIBUTES.each do |attr|
|
44
|
+
__send__("#{attr}=", nil)
|
45
|
+
end
|
46
|
+
vars = ATTRIBUTES.dup
|
47
|
+
vars.unshift(:do_validate)
|
48
|
+
attrs.each do |name, value|
|
49
|
+
if vars.include?(name.to_s)
|
50
|
+
__send__("#{name}=", value)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
rv = ""
|
57
|
+
if @href
|
58
|
+
rv << %Q[<?xml-stylesheet]
|
59
|
+
ATTRIBUTES.each do |name|
|
60
|
+
if __send__(name)
|
61
|
+
rv << %Q[ #{name}="#{h __send__(name)}"]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
rv << %Q[?>]
|
65
|
+
end
|
66
|
+
rv
|
67
|
+
end
|
68
|
+
|
69
|
+
remove_method(:href=)
|
70
|
+
def href=(value)
|
71
|
+
@href = value
|
72
|
+
if @href and @type.nil?
|
73
|
+
@type = guess_type(@href)
|
74
|
+
end
|
75
|
+
@href
|
76
|
+
end
|
77
|
+
|
78
|
+
remove_method(:alternate=)
|
79
|
+
def alternate=(value)
|
80
|
+
if value.nil? or /\A(?:yes|no)\z/ =~ value
|
81
|
+
@alternate = value
|
82
|
+
else
|
83
|
+
if @do_validate
|
84
|
+
args = ["?xml-stylesheet?", %Q[alternate="#{value}"]]
|
85
|
+
raise NotAvailableValueError.new(*args)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@alternate
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup_maker(maker)
|
92
|
+
xss = maker.xml_stylesheets.new_xml_stylesheet
|
93
|
+
ATTRIBUTES.each do |attr|
|
94
|
+
xss.__send__("#{attr}=", __send__(attr))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def guess_type(filename)
|
100
|
+
/\.([^.]+)$/ =~ filename
|
101
|
+
GUESS_TABLE[$1]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
data/lib/rss/xml.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require "rss/utils"
|
2
|
+
|
3
|
+
module RSS
|
4
|
+
module XML
|
5
|
+
class Element
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :name, :prefix, :uri, :attributes, :children
|
9
|
+
def initialize(name, prefix=nil, uri=nil, attributes={}, children=[])
|
10
|
+
@name = name
|
11
|
+
@prefix = prefix
|
12
|
+
@uri = uri
|
13
|
+
@attributes = attributes
|
14
|
+
if children.is_a?(String) or !children.respond_to?(:each)
|
15
|
+
@children = [children]
|
16
|
+
else
|
17
|
+
@children = children
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](name)
|
22
|
+
@attributes[name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(name, value)
|
26
|
+
@attributes[name] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def <<(child)
|
30
|
+
@children << child
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
@children.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
other.kind_of?(self.class) and
|
39
|
+
@name == other.name and
|
40
|
+
@uri == other.uri and
|
41
|
+
@attributes == other.attributes and
|
42
|
+
@children == other.children
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
rv = "<#{full_name}"
|
47
|
+
attributes.each do |key, value|
|
48
|
+
rv << " #{Utils.html_escape(key)}=\"#{Utils.html_escape(value)}\""
|
49
|
+
end
|
50
|
+
if children.empty?
|
51
|
+
rv << "/>"
|
52
|
+
else
|
53
|
+
rv << ">"
|
54
|
+
children.each do |child|
|
55
|
+
rv << child.to_s
|
56
|
+
end
|
57
|
+
rv << "</#{full_name}>"
|
58
|
+
end
|
59
|
+
rv
|
60
|
+
end
|
61
|
+
|
62
|
+
def full_name
|
63
|
+
if @prefix
|
64
|
+
"#{@prefix}:#{@name}"
|
65
|
+
else
|
66
|
+
@name
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
begin
|
2
|
+
require "xml/parser"
|
3
|
+
rescue LoadError
|
4
|
+
require "xmlparser"
|
5
|
+
end
|
6
|
+
|
7
|
+
begin
|
8
|
+
require "xml/encoding-ja"
|
9
|
+
rescue LoadError
|
10
|
+
require "xmlencoding-ja"
|
11
|
+
if defined?(Kconv)
|
12
|
+
module XMLEncoding_ja
|
13
|
+
class SJISHandler
|
14
|
+
include Kconv
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module XML
|
21
|
+
class Parser
|
22
|
+
unless defined?(Error)
|
23
|
+
Error = ::XMLParserError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module RSS
|
29
|
+
|
30
|
+
class REXMLLikeXMLParser < ::XML::Parser
|
31
|
+
|
32
|
+
include ::XML::Encoding_ja
|
33
|
+
|
34
|
+
def listener=(listener)
|
35
|
+
@listener = listener
|
36
|
+
end
|
37
|
+
|
38
|
+
def startElement(name, attrs)
|
39
|
+
@listener.tag_start(name, attrs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def endElement(name)
|
43
|
+
@listener.tag_end(name)
|
44
|
+
end
|
45
|
+
|
46
|
+
def character(data)
|
47
|
+
@listener.text(data)
|
48
|
+
end
|
49
|
+
|
50
|
+
def xmlDecl(version, encoding, standalone)
|
51
|
+
@listener.xmldecl(version, encoding, standalone == 1)
|
52
|
+
end
|
53
|
+
|
54
|
+
def processingInstruction(target, content)
|
55
|
+
@listener.instruction(target, content)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
class XMLParserParser < BaseParser
|
61
|
+
|
62
|
+
class << self
|
63
|
+
def listener
|
64
|
+
XMLParserListener
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def _parse
|
70
|
+
begin
|
71
|
+
parser = REXMLLikeXMLParser.new
|
72
|
+
parser.listener = @listener
|
73
|
+
parser.parse(@rss)
|
74
|
+
rescue ::XML::Parser::Error => e
|
75
|
+
raise NotWellFormedError.new(parser.line){e.message}
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class XMLParserListener < BaseListener
|
82
|
+
|
83
|
+
include ListenerMixin
|
84
|
+
|
85
|
+
def xmldecl(version, encoding, standalone)
|
86
|
+
super
|
87
|
+
# Encoding is converted to UTF-8 when XMLParser parses XML.
|
88
|
+
@encoding = 'UTF-8'
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'xmlscan/scanner'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module RSS
|
5
|
+
|
6
|
+
class XMLScanParser < BaseParser
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def listener
|
10
|
+
XMLScanListener
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def _parse
|
16
|
+
begin
|
17
|
+
if @rss.is_a?(String)
|
18
|
+
input = StringIO.new(@rss)
|
19
|
+
else
|
20
|
+
input = @rss
|
21
|
+
end
|
22
|
+
scanner = XMLScan::XMLScanner.new(@listener)
|
23
|
+
scanner.parse(input)
|
24
|
+
rescue XMLScan::Error => e
|
25
|
+
lineno = e.lineno || scanner.lineno || input.lineno
|
26
|
+
raise NotWellFormedError.new(lineno){e.message}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class XMLScanListener < BaseListener
|
33
|
+
|
34
|
+
include XMLScan::Visitor
|
35
|
+
include ListenerMixin
|
36
|
+
|
37
|
+
ENTITIES = {
|
38
|
+
'lt' => '<',
|
39
|
+
'gt' => '>',
|
40
|
+
'amp' => '&',
|
41
|
+
'quot' => '"',
|
42
|
+
'apos' => '\''
|
43
|
+
}
|
44
|
+
|
45
|
+
def on_xmldecl_version(str)
|
46
|
+
@version = str
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_xmldecl_encoding(str)
|
50
|
+
@encoding = str
|
51
|
+
end
|
52
|
+
|
53
|
+
def on_xmldecl_standalone(str)
|
54
|
+
@standalone = str
|
55
|
+
end
|
56
|
+
|
57
|
+
def on_xmldecl_end
|
58
|
+
xmldecl(@version, @encoding, @standalone == "yes")
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method(:on_pi, :instruction)
|
62
|
+
alias_method(:on_chardata, :text)
|
63
|
+
alias_method(:on_cdata, :text)
|
64
|
+
|
65
|
+
def on_etag(name)
|
66
|
+
tag_end(name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_entityref(ref)
|
70
|
+
text(entity(ref))
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_charref(code)
|
74
|
+
text([code].pack('U'))
|
75
|
+
end
|
76
|
+
|
77
|
+
alias_method(:on_charref_hex, :on_charref)
|
78
|
+
|
79
|
+
def on_stag(name)
|
80
|
+
@attrs = {}
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_attribute(name)
|
84
|
+
@attrs[name] = @current_attr = ''
|
85
|
+
end
|
86
|
+
|
87
|
+
def on_attr_value(str)
|
88
|
+
@current_attr << str
|
89
|
+
end
|
90
|
+
|
91
|
+
def on_attr_entityref(ref)
|
92
|
+
@current_attr << entity(ref)
|
93
|
+
end
|
94
|
+
|
95
|
+
def on_attr_charref(code)
|
96
|
+
@current_attr << [code].pack('U')
|
97
|
+
end
|
98
|
+
|
99
|
+
alias_method(:on_attr_charref_hex, :on_attr_charref)
|
100
|
+
|
101
|
+
def on_stag_end(name)
|
102
|
+
tag_start(name, @attrs)
|
103
|
+
end
|
104
|
+
|
105
|
+
def on_stag_end_empty(name)
|
106
|
+
tag_start(name, @attrs)
|
107
|
+
tag_end(name)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def entity(ref)
|
112
|
+
ent = ENTITIES[ref]
|
113
|
+
if ent
|
114
|
+
ent
|
115
|
+
else
|
116
|
+
wellformed_error("undefined entity: #{ref}")
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|