feedtools 0.2.17 → 0.2.18
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/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,
|