feedtools 0.2.17 → 0.2.18
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -0
- data/lib/feed_tools.rb +34 -12
- data/lib/feed_tools/database_feed_cache.rb +24 -0
- data/lib/feed_tools/feed.rb +238 -82
- data/lib/feed_tools/feed_item.rb +218 -106
- data/lib/feed_tools/helpers/feed_helper.rb +27 -0
- data/lib/feed_tools/helpers/feed_item_helper.rb +27 -0
- data/lib/feed_tools/helpers/feed_tools_helper.rb +78 -0
- data/lib/feed_tools/helpers/generic_helper.rb +36 -0
- data/lib/feed_tools/helpers/module_helper.rb +27 -0
- data/rakefile +2 -2
- data/test/unit/amp_test.rb +406 -398
- data/test/unit/atom_test.rb +41 -16
- data/test/unit/cdf_test.rb +55 -49
- data/test/unit/generation_test.rb +1 -1
- data/test/unit/helper_test.rb +4 -0
- data/test/unit/nonstandard_test.rb +58 -50
- data/test/unit/rss_test.rb +510 -530
- metadata +8 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
== FeedTools 0.2.18
|
2
|
+
* no longer ever polls more often than once every 30 minutes
|
3
|
+
* fixed overlooked improperly refactored enclosure code
|
4
|
+
* fixed issue with inner_xml incorrectly handling xml comments
|
5
|
+
* added helper modules
|
6
|
+
* test cases now implemented using helpers
|
7
|
+
* fixed issue with timeouts
|
8
|
+
* fixed stack overflow while estimating timestamps
|
1
9
|
== FeedTools 0.2.17
|
2
10
|
* more fixes for timestamping of feed items
|
3
11
|
* fixed nil bug in root_node, feed_type, feed_version, build_xml
|
data/lib/feed_tools.rb
CHANGED
@@ -32,13 +32,13 @@ FEED_TOOLS_ENV = ENV['FEED_TOOLS_ENV'] ||
|
|
32
32
|
ENV['RAILS_ENV'] ||
|
33
33
|
'production' # :nodoc:
|
34
34
|
|
35
|
-
FEED_TOOLS_VERSION = "0.2.
|
35
|
+
FEED_TOOLS_VERSION = "0.2.18"
|
36
36
|
|
37
37
|
FEED_TOOLS_NAMESPACES = {
|
38
38
|
"admin" => "http://webns.net/mvcb/",
|
39
39
|
"ag" => "http://purl.org/rss/1.0/modules/aggregation/",
|
40
40
|
"annotate" => "http://purl.org/rss/1.0/modules/annotate/",
|
41
|
-
"
|
41
|
+
"atom10" => "http://www.w3.org/2005/Atom",
|
42
42
|
"atom03" => "http://purl.org/atom/ns#",
|
43
43
|
"audio" => "http://media.tangent.org/rss/1.0/",
|
44
44
|
"blogChannel" => "http://backend.userland.com/blogChannelModule",
|
@@ -123,6 +123,7 @@ begin
|
|
123
123
|
require 'cgi'
|
124
124
|
require 'pp'
|
125
125
|
require 'yaml'
|
126
|
+
require 'base64'
|
126
127
|
|
127
128
|
require_gem('activerecord', '>= 1.10.1')
|
128
129
|
require_gem('uuidtools', '>= 0.1.2')
|
@@ -611,25 +612,42 @@ module FeedTools
|
|
611
612
|
end
|
612
613
|
|
613
614
|
# Creates a merged "planet" feed from a set of urls.
|
614
|
-
|
615
|
+
#
|
616
|
+
# Options are:
|
617
|
+
# * <tt>:multi_threaded</tt> - If set to true, feeds will
|
618
|
+
# be retrieved concurrently. Not recommended when used
|
619
|
+
# in conjunction with the DatabaseFeedCache as it will
|
620
|
+
# open multiple connections to the database.
|
621
|
+
def FeedTools.build_merged_feed(url_array, options = {})
|
622
|
+
validate_options([ :multi_threaded ],
|
623
|
+
options.keys)
|
624
|
+
options = { :multi_threaded => false }.merge(options)
|
615
625
|
return nil if url_array.nil?
|
616
626
|
merged_feed = FeedTools::Feed.new
|
617
627
|
retrieved_feeds = []
|
618
|
-
|
619
|
-
|
620
|
-
|
628
|
+
if options[:multi_threaded]
|
629
|
+
feed_threads = []
|
630
|
+
url_array.each do |feed_url|
|
631
|
+
feed_threads << Thread.new do
|
632
|
+
feed = Feed.open(feed_url)
|
633
|
+
retrieved_feeds << feed
|
634
|
+
end
|
635
|
+
end
|
636
|
+
feed_threads.each do |thread|
|
637
|
+
thread.join
|
638
|
+
end
|
639
|
+
else
|
640
|
+
url_array.each do |feed_url|
|
621
641
|
feed = Feed.open(feed_url)
|
622
642
|
retrieved_feeds << feed
|
623
643
|
end
|
624
644
|
end
|
625
|
-
feed_threads.each do |thread|
|
626
|
-
thread.join
|
627
|
-
end
|
628
645
|
retrieved_feeds.each do |feed|
|
629
646
|
merged_feed.entries.concat(
|
630
647
|
feed.entries.collect do |entry|
|
631
|
-
|
632
|
-
entry
|
648
|
+
new_entry = entry.dup
|
649
|
+
new_entry.title = "#{feed.title}: #{entry.title}"
|
650
|
+
new_entry
|
633
651
|
end )
|
634
652
|
end
|
635
653
|
return merged_feed
|
@@ -642,7 +660,11 @@ module REXML # :nodoc:
|
|
642
660
|
def inner_xml # :nodoc:
|
643
661
|
result = ""
|
644
662
|
self.each_child do |child|
|
645
|
-
|
663
|
+
if child.kind_of? REXML::Comment
|
664
|
+
result << "<!--" + child.to_s + "-->"
|
665
|
+
else
|
666
|
+
result << child.to_s
|
667
|
+
end
|
646
668
|
end
|
647
669
|
return result
|
648
670
|
end
|
@@ -1,3 +1,26 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005 Robert Aman
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
1
24
|
#= database_feed_cache.rb
|
2
25
|
#
|
3
26
|
# The <tt>DatabaseFeedCache</tt> is the default caching mechanism for
|
@@ -22,6 +45,7 @@ module FeedTools
|
|
22
45
|
def DatabaseFeedCache.initialize_cache
|
23
46
|
# Establish a connection if we don't already have one
|
24
47
|
begin
|
48
|
+
ActiveRecord::Base.default_timezone = :utc
|
25
49
|
ActiveRecord::Base.connection
|
26
50
|
rescue
|
27
51
|
begin
|
data/lib/feed_tools/feed.rb
CHANGED
@@ -1,7 +1,38 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2005 Robert Aman
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'feed_tools/helpers/generic_helper'
|
25
|
+
|
1
26
|
module FeedTools
|
2
27
|
# The <tt>FeedTools::Feed</tt> class represents a web feed's structure.
|
3
|
-
class Feed
|
4
|
-
|
28
|
+
class Feed
|
29
|
+
# :stopdoc:
|
30
|
+
include REXML
|
31
|
+
class << self
|
32
|
+
include GenericHelper
|
33
|
+
private :validate_options
|
34
|
+
end
|
35
|
+
# :startdoc:
|
5
36
|
|
6
37
|
# Represents a feed/feed item's category
|
7
38
|
class Category
|
@@ -115,17 +146,7 @@ module FeedTools
|
|
115
146
|
@items = nil
|
116
147
|
@live = false
|
117
148
|
end
|
118
|
-
|
119
|
-
# Raises an exception if an invalid option has been specified to
|
120
|
-
# prevent misspellings from slipping through
|
121
|
-
def Feed.validate_options(valid_option_keys, supplied_option_keys)
|
122
|
-
unknown_option_keys = supplied_option_keys - valid_option_keys
|
123
|
-
unless unknown_option_keys.empty?
|
124
|
-
raise ArgumentError, "Unknown options: #{unknown_option_keys}"
|
125
|
-
end
|
126
|
-
end
|
127
|
-
class << self; private :validate_options; end
|
128
|
-
|
149
|
+
|
129
150
|
# Loads the feed specified by the url, pulling the data from the
|
130
151
|
# cache if it hasn't expired.
|
131
152
|
# Options are:
|
@@ -319,6 +340,8 @@ module FeedTools
|
|
319
340
|
end
|
320
341
|
rescue SocketError
|
321
342
|
raise FeedAccessError, 'Socket error prevented feed retrieval'
|
343
|
+
rescue Timeout::Error
|
344
|
+
raise FeedAccessError, 'Timeout while attempting to retrieve feed'
|
322
345
|
end
|
323
346
|
end
|
324
347
|
|
@@ -699,9 +722,16 @@ module FeedTools
|
|
699
722
|
unless channel_node.nil?
|
700
723
|
@id = XPath.first(channel_node, "id/text()").to_s
|
701
724
|
if @id == ""
|
702
|
-
@id = XPath.first(channel_node, "
|
725
|
+
@id = XPath.first(channel_node, "atom10:id/text()",
|
726
|
+
FEED_TOOLS_NAMESPACES).to_s
|
727
|
+
end
|
728
|
+
if @id == ""
|
729
|
+
@id = XPath.first(channel_node, "atom03:id/text()",
|
703
730
|
FEED_TOOLS_NAMESPACES).to_s
|
704
731
|
end
|
732
|
+
if @id == ""
|
733
|
+
@id = XPath.first(channel_node, "atom:id/text()").to_s
|
734
|
+
end
|
705
735
|
if @id == ""
|
706
736
|
@id = XPath.first(channel_node, "guid/text()").to_s
|
707
737
|
end
|
@@ -711,9 +741,16 @@ module FeedTools
|
|
711
741
|
@id = XPath.first(root_node, "id/text()").to_s
|
712
742
|
end
|
713
743
|
if @id == ""
|
714
|
-
@id = XPath.first(
|
744
|
+
@id = XPath.first(channel_node, "atom10:id/text()",
|
745
|
+
FEED_TOOLS_NAMESPACES).to_s
|
746
|
+
end
|
747
|
+
if @id == ""
|
748
|
+
@id = XPath.first(channel_node, "atom03:id/text()",
|
715
749
|
FEED_TOOLS_NAMESPACES).to_s
|
716
750
|
end
|
751
|
+
if @id == ""
|
752
|
+
@id = XPath.first(channel_node, "atom:id/text()").to_s
|
753
|
+
end
|
717
754
|
if @id == ""
|
718
755
|
@id = XPath.first(root_node, "guid/text()").to_s
|
719
756
|
end
|
@@ -757,7 +794,7 @@ module FeedTools
|
|
757
794
|
@url = nil if @url == ""
|
758
795
|
end
|
759
796
|
if override_url.call
|
760
|
-
@url = XPath.first(channel_node, "
|
797
|
+
@url = XPath.first(channel_node, "atom10:link[@rel='self']/@href",
|
761
798
|
FEED_TOOLS_NAMESPACES).to_s
|
762
799
|
@url = nil if @url == ""
|
763
800
|
end
|
@@ -805,7 +842,22 @@ module FeedTools
|
|
805
842
|
if @title.nil?
|
806
843
|
unless channel_node.nil?
|
807
844
|
repair_entities = false
|
808
|
-
title_node = XPath.first(channel_node, "title"
|
845
|
+
title_node = XPath.first(channel_node, "atom10:title",
|
846
|
+
FEED_TOOLS_NAMESPACES)
|
847
|
+
if title_node.nil?
|
848
|
+
title_node = XPath.first(channel_node, "title")
|
849
|
+
end
|
850
|
+
if title_node.nil?
|
851
|
+
title_node = XPath.first(channel_node, "atom03:title",
|
852
|
+
FEED_TOOLS_NAMESPACES)
|
853
|
+
end
|
854
|
+
if title_node.nil?
|
855
|
+
title_node = XPath.first(channel_node, "atom:title")
|
856
|
+
end
|
857
|
+
if title_node.nil?
|
858
|
+
title_node = XPath.first(channel_node, "dc:title",
|
859
|
+
FEED_TOOLS_NAMESPACES)
|
860
|
+
end
|
809
861
|
if title_node.nil?
|
810
862
|
title_node = XPath.first(channel_node, "dc:title")
|
811
863
|
end
|
@@ -816,16 +868,21 @@ module FeedTools
|
|
816
868
|
if title_node.nil?
|
817
869
|
return nil
|
818
870
|
end
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
871
|
+
title_type = XPath.first(title_node, "@type").to_s
|
872
|
+
title_mode = XPath.first(title_node, "@mode").to_s
|
873
|
+
title_encoding = XPath.first(title_node, "@encoding").to_s
|
874
|
+
|
875
|
+
# Note that we're checking for misuse of type, mode and encoding here
|
876
|
+
if title_type == "base64" || title_mode == "base64" ||
|
877
|
+
title_encoding == "base64"
|
878
|
+
@title = Base64.decode64(title_node.inner_xml.strip)
|
879
|
+
elsif title_type == "xhtml" || title_mode == "xhtml" ||
|
880
|
+
title_type == "xml" || title_mode == "xml" ||
|
881
|
+
title_type == "application/xhtml+xml"
|
824
882
|
@title = title_node.inner_xml
|
825
|
-
elsif
|
826
|
-
XPath.first(title_node, "@mode").to_s == "escaped"
|
883
|
+
elsif title_type == "escaped" || title_mode == "escaped"
|
827
884
|
@title = FeedTools.unescape_entities(
|
828
|
-
|
885
|
+
title_node.inner_xml)
|
829
886
|
else
|
830
887
|
@title = title_node.inner_xml
|
831
888
|
repair_entities = true
|
@@ -900,27 +957,29 @@ module FeedTools
|
|
900
957
|
if description_node.nil?
|
901
958
|
return nil
|
902
959
|
end
|
903
|
-
|
904
|
-
|
905
|
-
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
912
|
-
|
913
|
-
|
914
|
-
|
915
|
-
|
916
|
-
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
|
921
|
-
|
922
|
-
|
923
|
-
|
960
|
+
description_type = XPath.first(description_node, "@type").to_s
|
961
|
+
description_mode = XPath.first(description_node, "@mode").to_s
|
962
|
+
description_encoding = XPath.first(description_node, "@encoding").to_s
|
963
|
+
|
964
|
+
# Note that we're checking for misuse of type, mode and encoding here
|
965
|
+
if description_encoding != ""
|
966
|
+
@description =
|
967
|
+
"[Embedded data objects are not currently supported.]"
|
968
|
+
elsif description_node.cdatas.size > 0
|
969
|
+
@description = description_node.cdatas.first.value
|
970
|
+
elsif description_type == "base64" || description_mode == "base64" ||
|
971
|
+
description_encoding == "base64"
|
972
|
+
@description = Base64.decode64(description_node.inner_xml.strip)
|
973
|
+
elsif description_type == "xhtml" || description_mode == "xhtml" ||
|
974
|
+
description_type == "xml" || description_mode == "xml" ||
|
975
|
+
description_type == "application/xhtml+xml"
|
976
|
+
@description = description_node.inner_xml
|
977
|
+
elsif description_type == "escaped" || description_mode == "escaped"
|
978
|
+
@description = FeedTools.unescape_entities(
|
979
|
+
description_node.inner_xml)
|
980
|
+
else
|
981
|
+
@description = description_node.inner_xml
|
982
|
+
repair_entities = true
|
924
983
|
end
|
925
984
|
if @description == ""
|
926
985
|
@description = self.itunes_summary
|
@@ -1106,18 +1165,34 @@ module FeedTools
|
|
1106
1165
|
if @author.nil?
|
1107
1166
|
@author = FeedTools::Feed::Author.new
|
1108
1167
|
unless channel_node.nil?
|
1109
|
-
author_node = XPath.first(channel_node, "author"
|
1168
|
+
author_node = XPath.first(channel_node, "atom10:author",
|
1169
|
+
FEED_TOOLS_NAMESPACES)
|
1170
|
+
if author_node.nil?
|
1171
|
+
author_node = XPath.first(channel_node, "atom03:author",
|
1172
|
+
FEED_TOOLS_NAMESPACES)
|
1173
|
+
end
|
1174
|
+
if author_node.nil?
|
1175
|
+
author_node = XPath.first(channel_node, "atom:author")
|
1176
|
+
end
|
1177
|
+
if author_node.nil?
|
1178
|
+
author_node = XPath.first(channel_node, "author")
|
1179
|
+
end
|
1110
1180
|
if author_node.nil?
|
1111
1181
|
author_node = XPath.first(channel_node, "managingEditor")
|
1112
1182
|
end
|
1183
|
+
if author_node.nil?
|
1184
|
+
author_node = XPath.first(channel_node, "dc:author",
|
1185
|
+
FEED_TOOLS_NAMESPACES)
|
1186
|
+
end
|
1113
1187
|
if author_node.nil?
|
1114
1188
|
author_node = XPath.first(channel_node, "dc:author")
|
1115
1189
|
end
|
1116
1190
|
if author_node.nil?
|
1117
|
-
author_node = XPath.first(channel_node, "dc:creator"
|
1191
|
+
author_node = XPath.first(channel_node, "dc:creator",
|
1192
|
+
FEED_TOOLS_NAMESPACES)
|
1118
1193
|
end
|
1119
1194
|
if author_node.nil?
|
1120
|
-
author_node = XPath.first(channel_node, "
|
1195
|
+
author_node = XPath.first(channel_node, "dc:creator")
|
1121
1196
|
end
|
1122
1197
|
end
|
1123
1198
|
unless author_node.nil?
|
@@ -1316,7 +1391,7 @@ module FeedTools
|
|
1316
1391
|
end
|
1317
1392
|
begin
|
1318
1393
|
if time_string != nil && time_string != ""
|
1319
|
-
@time = Time.parse(time_string)
|
1394
|
+
@time = Time.parse(time_string).gmtime
|
1320
1395
|
else
|
1321
1396
|
@time = Time.now.gmtime
|
1322
1397
|
end
|
@@ -1342,7 +1417,7 @@ module FeedTools
|
|
1342
1417
|
end
|
1343
1418
|
end
|
1344
1419
|
if updated_string != nil && updated_string != ""
|
1345
|
-
@updated = Time.parse(updated_string) rescue nil
|
1420
|
+
@updated = Time.parse(updated_string).gmtime rescue nil
|
1346
1421
|
else
|
1347
1422
|
@updated = nil
|
1348
1423
|
end
|
@@ -1371,7 +1446,7 @@ module FeedTools
|
|
1371
1446
|
end
|
1372
1447
|
end
|
1373
1448
|
if issued_string != nil && issued_string != ""
|
1374
|
-
@issued = Time.parse(issued_string) rescue nil
|
1449
|
+
@issued = Time.parse(issued_string).gmtime rescue nil
|
1375
1450
|
else
|
1376
1451
|
@issued = nil
|
1377
1452
|
end
|
@@ -1400,7 +1475,7 @@ module FeedTools
|
|
1400
1475
|
end
|
1401
1476
|
end
|
1402
1477
|
if published_string != nil && published_string != ""
|
1403
|
-
@published = Time.parse(published_string) rescue nil
|
1478
|
+
@published = Time.parse(published_string).gmtime rescue nil
|
1404
1479
|
else
|
1405
1480
|
@published = nil
|
1406
1481
|
end
|
@@ -1531,20 +1606,70 @@ module FeedTools
|
|
1531
1606
|
# Returns the feed's copyright information
|
1532
1607
|
def copyright
|
1533
1608
|
if @copyright.nil?
|
1534
|
-
unless
|
1535
|
-
|
1536
|
-
|
1537
|
-
|
1609
|
+
unless root_node.nil?
|
1610
|
+
repair_entities = false
|
1611
|
+
copyright_node = XPath.first(channel_node, "dc:rights")
|
1612
|
+
if copyright_node.nil?
|
1613
|
+
copyright_node = XPath.first(channel_node, "dc:rights",
|
1614
|
+
FEED_TOOLS_NAMESPACES)
|
1615
|
+
end
|
1616
|
+
if copyright_node.nil?
|
1617
|
+
copyright_node = XPath.first(channel_node, "rights",
|
1618
|
+
FEED_TOOLS_NAMESPACES)
|
1538
1619
|
end
|
1539
|
-
if
|
1540
|
-
|
1620
|
+
if copyright_node.nil?
|
1621
|
+
copyright_node = XPath.first(channel_node, "copyright",
|
1622
|
+
FEED_TOOLS_NAMESPACES)
|
1541
1623
|
end
|
1542
|
-
if
|
1543
|
-
|
1624
|
+
if copyright_node.nil?
|
1625
|
+
copyright_node = XPath.first(channel_node, "atom03:copyright",
|
1626
|
+
FEED_TOOLS_NAMESPACES)
|
1544
1627
|
end
|
1628
|
+
if copyright_node.nil?
|
1629
|
+
copyright_node = XPath.first(channel_node, "atom10:copyright",
|
1630
|
+
FEED_TOOLS_NAMESPACES)
|
1631
|
+
end
|
1632
|
+
if copyright_node.nil?
|
1633
|
+
copyright_node = XPath.first(channel_node, "copyrights",
|
1634
|
+
FEED_TOOLS_NAMESPACES)
|
1635
|
+
end
|
1636
|
+
end
|
1637
|
+
if copyright_node.nil?
|
1638
|
+
return nil
|
1639
|
+
end
|
1640
|
+
copyright_type = XPath.first(copyright_node, "@type").to_s
|
1641
|
+
copyright_mode = XPath.first(copyright_node, "@mode").to_s
|
1642
|
+
copyright_encoding = XPath.first(copyright_node, "@encoding").to_s
|
1643
|
+
|
1644
|
+
# Note that we're checking for misuse of type, mode and encoding here
|
1645
|
+
if copyright_encoding != ""
|
1646
|
+
@copyright =
|
1647
|
+
"[Embedded data objects are not currently supported.]"
|
1648
|
+
elsif copyright_node.cdatas.size > 0
|
1649
|
+
@copyright = copyright_node.cdatas.first.value
|
1650
|
+
elsif copyright_type == "base64" || copyright_mode == "base64" ||
|
1651
|
+
copyright_encoding == "base64"
|
1652
|
+
@copyright = Base64.decode64(copyright_node.inner_xml.strip)
|
1653
|
+
elsif copyright_type == "xhtml" || copyright_mode == "xhtml" ||
|
1654
|
+
copyright_type == "xml" || copyright_mode == "xml" ||
|
1655
|
+
copyright_type == "application/xhtml+xml"
|
1656
|
+
@copyright = copyright_node.inner_xml
|
1657
|
+
elsif copyright_type == "escaped" || copyright_mode == "escaped"
|
1658
|
+
@copyright = FeedTools.unescape_entities(
|
1659
|
+
copyright_node.inner_xml)
|
1660
|
+
else
|
1661
|
+
@copyright = copyright_node.inner_xml
|
1662
|
+
repair_entities = true
|
1663
|
+
end
|
1664
|
+
|
1665
|
+
unless @copyright.nil?
|
1545
1666
|
@copyright = FeedTools.sanitize_html(@copyright, :strip)
|
1546
|
-
@copyright =
|
1667
|
+
@copyright = FeedTools.unescape_entities(@copyright) if repair_entities
|
1668
|
+
@copyright = FeedTools.tidy_html(@copyright)
|
1547
1669
|
end
|
1670
|
+
|
1671
|
+
@copyright = @copyright.strip unless @copyright.nil?
|
1672
|
+
@copyright = nil if @copyright == ""
|
1548
1673
|
end
|
1549
1674
|
return @copyright
|
1550
1675
|
end
|
@@ -1596,15 +1721,16 @@ module FeedTools
|
|
1596
1721
|
@time_to_live = update_frequency.to_i.year
|
1597
1722
|
elsif update_frequency.to_i >= 3000
|
1598
1723
|
# Normally, this should default to minutes, but realistically,
|
1599
|
-
# if they meant minutes, you're rarely going to see a value
|
1600
|
-
# than 120. If we see >= 3000, we're either dealing
|
1601
|
-
# pseudo-spec that decided to use seconds, or
|
1602
|
-
# someone who only has weekly updated
|
1603
|
-
# misreport the time, and we update
|
1604
|
-
#
|
1605
|
-
#
|
1606
|
-
# is a far greater one than
|
1607
|
-
# and hope no one
|
1724
|
+
# if they meant minutes, you're rarely going to see a value
|
1725
|
+
# higher than 120. If we see >= 3000, we're either dealing
|
1726
|
+
# with a stupid pseudo-spec that decided to use seconds, or
|
1727
|
+
# we're looking at someone who only has weekly updated
|
1728
|
+
# content. Worst case, we misreport the time, and we update
|
1729
|
+
# too often. Best case, we avoid accidentally updating the
|
1730
|
+
# feed only once a year. In the interests of being pragmatic,
|
1731
|
+
# and since the problem we avoid is a far greater one than
|
1732
|
+
# the one we cause, just run the check and hope no one
|
1733
|
+
# actually gets hurt.
|
1608
1734
|
@time_to_live = update_frequency.to_i
|
1609
1735
|
else
|
1610
1736
|
@time_to_live = update_frequency.to_i.minute
|
@@ -1628,7 +1754,8 @@ module FeedTools
|
|
1628
1754
|
@time_to_live = @time_to_live + update_frequency_hours.to_i.hour
|
1629
1755
|
end
|
1630
1756
|
if update_frequency_minutes != ""
|
1631
|
-
@time_to_live = @time_to_live +
|
1757
|
+
@time_to_live = @time_to_live +
|
1758
|
+
update_frequency_minutes.to_i.minute
|
1632
1759
|
end
|
1633
1760
|
if update_frequency_seconds != ""
|
1634
1761
|
@time_to_live = @time_to_live + update_frequency_seconds.to_i
|
@@ -1790,7 +1917,6 @@ module FeedTools
|
|
1790
1917
|
new_item = FeedItem.new
|
1791
1918
|
new_item.feed_data = item_node.to_s
|
1792
1919
|
new_item.feed_data_type = self.feed_data_type
|
1793
|
-
new_item.feed = self
|
1794
1920
|
@items << new_item
|
1795
1921
|
end
|
1796
1922
|
end
|
@@ -1802,9 +1928,30 @@ module FeedTools
|
|
1802
1928
|
end
|
1803
1929
|
return @items
|
1804
1930
|
end
|
1931
|
+
|
1932
|
+
# Sets the items array to a new array.
|
1933
|
+
def items=(new_items)
|
1934
|
+
for item in new_items
|
1935
|
+
unless item.kind_of? FeedTools::FeedItem
|
1936
|
+
raise ArgumentError,
|
1937
|
+
"You should only add FeedItem objects to the items array."
|
1938
|
+
end
|
1939
|
+
end
|
1940
|
+
@items = new_items
|
1941
|
+
end
|
1942
|
+
|
1943
|
+
# Syntactic sugar for appending feed items to a feed.
|
1944
|
+
def <<(new_item)
|
1945
|
+
@items ||= []
|
1946
|
+
unless new_item.kind_of? FeedTools::FeedItem
|
1947
|
+
raise ArgumentError,
|
1948
|
+
"You should only add FeedItem objects to the items array."
|
1949
|
+
end
|
1950
|
+
@items << new_item
|
1951
|
+
end
|
1805
1952
|
|
1806
|
-
# The time that the feed was last requested from the remote server. Nil
|
1807
|
-
# never been pulled, or if it was created from scratch.
|
1953
|
+
# The time that the feed was last requested from the remote server. Nil
|
1954
|
+
# if it has never been pulled, or if it was created from scratch.
|
1808
1955
|
def last_retrieved
|
1809
1956
|
unless self.cache_object.nil?
|
1810
1957
|
@last_retrieved = self.cache_object.last_retrieved
|
@@ -1850,8 +1997,13 @@ module FeedTools
|
|
1850
1997
|
# True if the feed has expired and must be reacquired from the remote
|
1851
1998
|
# server.
|
1852
1999
|
def expired?
|
1853
|
-
|
1854
|
-
|
2000
|
+
if (self.last_retrieved == nil)
|
2001
|
+
return true
|
2002
|
+
elsif (self.time_to_live < 30.minutes)
|
2003
|
+
return (self.last_retrieved + 30.minutes) < Time.now.gmtime
|
2004
|
+
else
|
2005
|
+
return (self.last_retrieved + self.time_to_live) < Time.now.gmtime
|
2006
|
+
end
|
1855
2007
|
end
|
1856
2008
|
|
1857
2009
|
# Forces this feed to expire.
|
@@ -1914,16 +2066,19 @@ module FeedTools
|
|
1914
2066
|
xml_builder.tag!("dc:language", language)
|
1915
2067
|
end
|
1916
2068
|
xml_builder.tag!("syn:updatePeriod", "hourly")
|
1917
|
-
xml_builder.tag!("syn:updateFrequency",
|
2069
|
+
xml_builder.tag!("syn:updateFrequency",
|
2070
|
+
(time_to_live / 1.hour).to_s)
|
1918
2071
|
xml_builder.tag!("syn:updateBase", Time.mktime(1970).iso8601)
|
1919
2072
|
xml_builder.items do
|
1920
2073
|
xml_builder.tag!("rdf:Seq") do
|
1921
2074
|
unless items.nil?
|
1922
2075
|
for item in items
|
1923
2076
|
if item.link.nil?
|
1924
|
-
raise "Cannot generate an rdf-based feed with a nil
|
2077
|
+
raise "Cannot generate an rdf-based feed with a nil " +
|
2078
|
+
"item link field."
|
1925
2079
|
end
|
1926
|
-
xml_builder.tag!("rdf:li", "rdf:resource" =>
|
2080
|
+
xml_builder.tag!("rdf:li", "rdf:resource" =>
|
2081
|
+
CGI.escapeHTML(item.link))
|
1927
2082
|
end
|
1928
2083
|
end
|
1929
2084
|
end
|
@@ -1939,7 +2094,8 @@ module FeedTools
|
|
1939
2094
|
end
|
1940
2095
|
end
|
1941
2096
|
best_image = images.first if best_image.nil?
|
1942
|
-
xml_builder.image(
|
2097
|
+
xml_builder.image(
|
2098
|
+
"rdf:about" => CGI.escapeHTML(best_image.url)) do
|
1943
2099
|
if best_image.title != nil && best_image.title != ""
|
1944
2100
|
xml_builder.title(best_image.title)
|
1945
2101
|
elsif self.title != nil && self.title != ""
|
@@ -2040,7 +2196,7 @@ module FeedTools
|
|
2040
2196
|
end
|
2041
2197
|
elsif feed_type == "atom" && version == 1.0
|
2042
2198
|
# normal atom format
|
2043
|
-
return xml_builder.feed("xmlns" => FEED_TOOLS_NAMESPACES['
|
2199
|
+
return xml_builder.feed("xmlns" => FEED_TOOLS_NAMESPACES['atom10'],
|
2044
2200
|
"xml:lang" => language) do
|
2045
2201
|
unless title.nil? || title == ""
|
2046
2202
|
xml_builder.title(title,
|