html2rss 0.23.0 → 0.25.0
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/html2rss/article.rb +42 -35
- data/lib/html2rss/auto_source/scraper/html.rb +68 -145
- data/lib/html2rss/auto_source/scraper/json_state/article_normalizer.rb +116 -0
- data/lib/html2rss/auto_source/scraper/json_state/candidate_detector.rb +63 -0
- data/lib/html2rss/auto_source/scraper/json_state/document_scanner.rb +179 -0
- data/lib/html2rss/auto_source/scraper/json_state/value_finder.rb +55 -0
- data/lib/html2rss/auto_source/scraper/json_state.rb +0 -379
- data/lib/html2rss/auto_source/scraper/schema/category_extractor.rb +29 -114
- data/lib/html2rss/auto_source/scraper/schema/item_list.rb +50 -11
- data/lib/html2rss/auto_source/scraper/schema/thing.rb +14 -5
- data/lib/html2rss/auto_source/scraper/schema.rb +61 -18
- data/lib/html2rss/auto_source/scraper/semantic_html/entry_deduplicator.rb +35 -30
- data/lib/html2rss/auto_source/scraper/semantic_html.rb +40 -120
- data/lib/html2rss/auto_source/scraper/sitemap/parser.rb +161 -0
- data/lib/html2rss/auto_source/scraper/sitemap.rb +10 -7
- data/lib/html2rss/auto_source/scraper/wordpress_api.rb +6 -2
- data/lib/html2rss/auto_source/scraper.rb +81 -44
- data/lib/html2rss/auto_source/segment.rb +29 -0
- data/lib/html2rss/auto_source/segmenter/cluster.rb +163 -0
- data/lib/html2rss/auto_source/segmenter/list.rb +101 -0
- data/lib/html2rss/auto_source/segmenter/primary_link.rb +81 -0
- data/lib/html2rss/auto_source/segmenter/semantic.rb +104 -0
- data/lib/html2rss/auto_source/segmenter.rb +66 -0
- data/lib/html2rss/auto_source.rb +74 -21
- data/lib/html2rss/channel.rb +159 -79
- data/lib/html2rss/cli.rb +8 -13
- data/lib/html2rss/config/auto_source_contract.rb +2 -0
- data/lib/html2rss/config/request_controls.rb +33 -0
- data/lib/html2rss/config/validator.rb +15 -8
- data/lib/html2rss/config.rb +6 -2
- data/lib/html2rss/feed_builder/item_presentation.rb +42 -0
- data/lib/html2rss/feed_builder/json_feed/item.rb +20 -4
- data/lib/html2rss/feed_builder/json_feed.rb +22 -2
- data/lib/html2rss/feed_builder/rss.rb +36 -24
- data/lib/html2rss/feed_builder.rb +9 -17
- data/lib/html2rss/feed_pipeline/auto_fallback.rb +71 -28
- data/lib/html2rss/feed_pipeline.rb +66 -55
- data/lib/html2rss/feed_result.rb +92 -0
- data/lib/html2rss/html/article_extractor/category_extractor.rb +10 -36
- data/lib/html2rss/html/article_extractor/date_extractor.rb +2 -7
- data/lib/html2rss/html/article_extractor/enclosure_extractor.rb +5 -50
- data/lib/html2rss/html/article_extractor/image_extractor.rb +5 -16
- data/lib/html2rss/html/article_rules/category.rb +55 -0
- data/lib/html2rss/html/article_rules/date.rb +25 -0
- data/lib/html2rss/html/article_rules/enclosure.rb +72 -0
- data/lib/html2rss/html/article_rules/image.rb +109 -0
- data/lib/html2rss/html/article_rules.rb +10 -0
- data/lib/html2rss/html/rendering/audio_renderer.rb +2 -12
- data/lib/html2rss/html/rendering/escaped_attributes.rb +27 -0
- data/lib/html2rss/html/rendering/image_renderer.rb +2 -12
- data/lib/html2rss/html/rendering/pdf_renderer.rb +2 -8
- data/lib/html2rss/html/rendering/video_renderer.rb +2 -12
- data/lib/html2rss/html/sst_article_extractor.rb +279 -0
- data/lib/html2rss/link_destination/destination_facts.rb +40 -0
- data/lib/html2rss/link_destination/noise_policy.rb +79 -0
- data/lib/html2rss/link_destination/path_classifier.rb +205 -0
- data/lib/html2rss/link_destination/text_classifier.rb +64 -0
- data/lib/html2rss/link_destination.rb +8 -0
- data/lib/html2rss/request_service/botasaurus_contract.rb +33 -5
- data/lib/html2rss/request_service/botasaurus_strategy.rb +13 -4
- data/lib/html2rss/request_service/budget.rb +7 -2
- data/lib/html2rss/request_service/faraday_strategy.rb +42 -1
- data/lib/html2rss/request_service/network_guard.rb +5 -3
- data/lib/html2rss/request_service/response.rb +9 -1
- data/lib/html2rss/request_service/strategy.rb +22 -0
- data/lib/html2rss/request_session.rb +4 -0
- data/lib/html2rss/scoring/anchor_score.rb +34 -0
- data/lib/html2rss/scoring/cluster_scorer.rb +87 -0
- data/lib/html2rss/scoring/container_assessor.rb +83 -0
- data/lib/html2rss/scoring/engine.rb +101 -0
- data/lib/html2rss/scoring/link_resolver.rb +72 -0
- data/lib/html2rss/scoring/observation.rb +53 -0
- data/lib/html2rss/scoring/ranked_segment.rb +45 -0
- data/lib/html2rss/scoring/score.rb +30 -0
- data/lib/html2rss/scoring.rb +33 -0
- data/lib/html2rss/selectors/post_processors/sanitize_html.rb +10 -1
- data/lib/html2rss/selectors.rb +26 -24
- data/lib/html2rss/sst/attrs.rb +91 -0
- data/lib/html2rss/sst/document.rb +23 -0
- data/lib/html2rss/sst/index.rb +112 -0
- data/lib/html2rss/sst/node.rb +147 -0
- data/lib/html2rss/sst/normalizer.rb +171 -0
- data/lib/html2rss/sst/tags.rb +26 -0
- data/lib/html2rss/sst/text.rb +81 -0
- data/lib/html2rss/sst.rb +8 -0
- data/lib/html2rss/status.rb +132 -0
- data/lib/html2rss/version.rb +1 -1
- data/lib/html2rss.rb +41 -29
- data/schema/html2rss-config.schema.json +20 -0
- metadata +45 -19
- data/lib/html2rss/auto_source/discovery/dom_clustering/group_scorer.rb +0 -80
- data/lib/html2rss/auto_source/discovery/dom_clustering/overlap_resolver.rb +0 -86
- data/lib/html2rss/auto_source/discovery/dom_clustering.rb +0 -119
- data/lib/html2rss/auto_source/discovery/list_candidates.rb +0 -94
- data/lib/html2rss/auto_source/discovery/semantic_anchor_candidates.rb +0 -219
- data/lib/html2rss/auto_source/discovery/semantic_containers.rb +0 -71
- data/lib/html2rss/auto_source/discovery/sitemap.rb +0 -159
- data/lib/html2rss/auto_source/discovery.rb +0 -14
- data/lib/html2rss/auto_source/link_heuristics/anchor_signals.rb +0 -28
- data/lib/html2rss/auto_source/link_heuristics/container_assessor.rb +0 -106
- data/lib/html2rss/auto_source/link_heuristics/container_signals.rb +0 -80
- data/lib/html2rss/auto_source/link_heuristics/destination_facts.rb +0 -42
- data/lib/html2rss/auto_source/link_heuristics/href_extractor.rb +0 -38
- data/lib/html2rss/auto_source/link_heuristics/path_classifier.rb +0 -221
- data/lib/html2rss/auto_source/link_heuristics/text_classifier.rb +0 -66
- data/lib/html2rss/auto_source/link_heuristics.rb +0 -139
- data/lib/html2rss/auto_source/scraper/schema/list_item.rb +0 -28
|
@@ -32,27 +32,16 @@ module Html2rss
|
|
|
32
32
|
# @see <https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images>
|
|
33
33
|
# @see <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#srcset>
|
|
34
34
|
# @see <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture>
|
|
35
|
-
def self.from_source(article_tag)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
next if url.nil? || url.start_with?('data:')
|
|
39
|
-
|
|
40
|
-
width_value = width.to_i.zero? ? 0 : width.scan(/\d+/).first.to_i
|
|
41
|
-
|
|
42
|
-
[width_value, url.strip]
|
|
43
|
-
end
|
|
44
|
-
end.compact.to_h
|
|
45
|
-
|
|
46
|
-
hash[hash.keys.max]
|
|
35
|
+
def self.from_source(article_tag)
|
|
36
|
+
srcsets = article_tag.css('img[srcset], picture > source[srcset]').map { |source| source['srcset'] }
|
|
37
|
+
ArticleRules::Image.largest_from_srcsets(srcsets)
|
|
47
38
|
end
|
|
48
39
|
|
|
49
40
|
# @param article_tag [Nokogiri::XML::Element] article container node
|
|
50
41
|
# @return [String, nil] best style-based background image URL
|
|
51
42
|
def self.from_style(article_tag)
|
|
52
|
-
article_tag.css('[style*="url"]')
|
|
53
|
-
|
|
54
|
-
.reject { |src| src.start_with?('data:') }
|
|
55
|
-
.max_by(&:size)
|
|
43
|
+
styles = article_tag.css('[style*="url"]').map { |tag| tag['style'] }
|
|
44
|
+
ArticleRules::Image.best_from_styles(styles)
|
|
56
45
|
end
|
|
57
46
|
end
|
|
58
47
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Html2rss
|
|
4
|
+
module Html
|
|
5
|
+
module ArticleRules
|
|
6
|
+
##
|
|
7
|
+
# Shared category term vocabulary and text accumulation (DOM walk stays in adapters).
|
|
8
|
+
module Category
|
|
9
|
+
# Class/data attribute tokens that mark category metadata.
|
|
10
|
+
CATEGORY_TERMS = %w[
|
|
11
|
+
category categories tag tags topic topics section sections
|
|
12
|
+
label labels theme themes subject subjects
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
# Regex matching category-related attribute or class names.
|
|
16
|
+
CATEGORY_ATTR_PATTERN = /#{CATEGORY_TERMS.join('|')}/i
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
##
|
|
20
|
+
# @param name [String]
|
|
21
|
+
# @return [Boolean]
|
|
22
|
+
def attr_name_match?(name)
|
|
23
|
+
name.to_s.match?(CATEGORY_ATTR_PATTERN)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @param class_attr [String, nil]
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def class_match?(class_attr)
|
|
30
|
+
class_attr.to_s.match?(CATEGORY_ATTR_PATTERN)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# @param categories [Set<String>]
|
|
35
|
+
# @param text [String, nil]
|
|
36
|
+
# @return [void]
|
|
37
|
+
def add_text!(categories, text)
|
|
38
|
+
value = text.to_s.strip
|
|
39
|
+
categories.add(value) unless value.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @param categories [Set<String>]
|
|
44
|
+
# @param text [String, nil]
|
|
45
|
+
# @return [void]
|
|
46
|
+
def add_split_text!(categories, text)
|
|
47
|
+
return unless text
|
|
48
|
+
|
|
49
|
+
text.split(/\n+/).each { |line| add_text!(categories, line) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Html2rss
|
|
4
|
+
module Html
|
|
5
|
+
module ArticleRules
|
|
6
|
+
##
|
|
7
|
+
# DOM-agnostic datetime aggregation for article published_at.
|
|
8
|
+
module Date
|
|
9
|
+
class << self
|
|
10
|
+
##
|
|
11
|
+
# @param datetime_strings [Array<String, nil>] raw datetime attribute values
|
|
12
|
+
# @return [DateTime, nil] earliest successfully parsed instant
|
|
13
|
+
def earliest(datetime_strings)
|
|
14
|
+
times = Array(datetime_strings).filter_map do |value|
|
|
15
|
+
DateTime.parse(value.to_s)
|
|
16
|
+
rescue ArgumentError, TypeError
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
times.min
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Html2rss
|
|
4
|
+
module Html
|
|
5
|
+
module ArticleRules
|
|
6
|
+
##
|
|
7
|
+
# DOM-agnostic enclosure URL/type normalization.
|
|
8
|
+
module Enclosure
|
|
9
|
+
# Href path suffixes treated as download/archive enclosure links.
|
|
10
|
+
ARCHIVE_HREF_SUFFIXES = %w[.pdf .zip .tar.gz .tgz].freeze
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
##
|
|
14
|
+
# @param href [String]
|
|
15
|
+
# @return [Boolean]
|
|
16
|
+
def archive_href?(href)
|
|
17
|
+
ARCHIVE_HREF_SUFFIXES.any? { |suffix| href.to_s.end_with?(suffix) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# @param src [String]
|
|
22
|
+
# @param base_url [String, Html2rss::Url]
|
|
23
|
+
# @return [Hash{Symbol => Object}, nil]
|
|
24
|
+
def from_image(src, base_url)
|
|
25
|
+
return if src.to_s.empty?
|
|
26
|
+
|
|
27
|
+
abs = Url.from_relative(src, base_url)
|
|
28
|
+
{ url: abs, type: Article::Enclosure.guess_content_type_from_url(abs, default: 'image/jpeg') }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# @param src [String]
|
|
33
|
+
# @param type [String, nil]
|
|
34
|
+
# @param base_url [String, Html2rss::Url]
|
|
35
|
+
# @return [Hash{Symbol => Object}, nil]
|
|
36
|
+
def from_media(src, type, base_url)
|
|
37
|
+
return if src.to_s.empty?
|
|
38
|
+
|
|
39
|
+
{ url: Url.from_relative(src, base_url), type: }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @param src [String]
|
|
44
|
+
# @param base_url [String, Html2rss::Url]
|
|
45
|
+
# @return [Hash{Symbol => Object}, nil]
|
|
46
|
+
def from_iframe(src, base_url)
|
|
47
|
+
return if src.to_s.empty?
|
|
48
|
+
|
|
49
|
+
abs = Url.from_relative(src, base_url)
|
|
50
|
+
{ url: abs, type: Article::Enclosure.guess_content_type_from_url(abs, default: 'text/html') }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# @param href [String]
|
|
55
|
+
# @param base_url [String, Html2rss::Url]
|
|
56
|
+
# @return [Hash{Symbol => Object}, nil]
|
|
57
|
+
def from_anchor(href, base_url)
|
|
58
|
+
return if href.to_s.empty?
|
|
59
|
+
|
|
60
|
+
abs = Url.from_relative(href, base_url)
|
|
61
|
+
type = if href.end_with?('.pdf')
|
|
62
|
+
Article::Enclosure.guess_content_type_from_url(abs)
|
|
63
|
+
else
|
|
64
|
+
'application/zip'
|
|
65
|
+
end
|
|
66
|
+
{ url: abs, type: }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Html2rss
|
|
4
|
+
module Html
|
|
5
|
+
module ArticleRules
|
|
6
|
+
##
|
|
7
|
+
# DOM-agnostic image URL selection (srcset sizing, CSS background urls).
|
|
8
|
+
#
|
|
9
|
+
# Uses string scans instead of Regexp: CodeQL rb/polynomial-redos flags the
|
|
10
|
+
# prior patterns on uncontrolled HTML attributes, and length guards alone
|
|
11
|
+
# do not clear the query.
|
|
12
|
+
module Image
|
|
13
|
+
# Skip absurdly large attributes before scanning.
|
|
14
|
+
MAX_ATTR_CHARS = 4_096
|
|
15
|
+
# Optional quoting around a CSS `url(...)` value.
|
|
16
|
+
QUOTES = ['"', "'"].freeze
|
|
17
|
+
# Srcset width/height descriptor suffixes.
|
|
18
|
+
UNITS = %w[w h].freeze
|
|
19
|
+
# HTML/CSS whitespace treated as srcset token separators.
|
|
20
|
+
WS = [' ', "\t", "\n", "\r", "\f"].freeze
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
##
|
|
24
|
+
# @param srcset_strings [Array<String>] raw srcset attribute values
|
|
25
|
+
# @return [String, nil] URL of the largest width/height candidate
|
|
26
|
+
def largest_from_srcsets(srcset_strings)
|
|
27
|
+
by_size = {}
|
|
28
|
+
Array(srcset_strings).each { |srcset| merge_srcset!(by_size, srcset) }
|
|
29
|
+
by_size[by_size.keys.max]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @param style_strings [Array<String>] CSS style attribute values
|
|
34
|
+
# @return [String, nil] longest non-data background url
|
|
35
|
+
def best_from_styles(style_strings)
|
|
36
|
+
Array(style_strings)
|
|
37
|
+
.filter_map { |style| style_url(style) }
|
|
38
|
+
.reject { |src| src.start_with?('data:') }
|
|
39
|
+
.max_by(&:size)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def merge_srcset!(by_size, srcset)
|
|
45
|
+
s = srcset.to_s
|
|
46
|
+
return if s.length > MAX_ATTR_CHARS
|
|
47
|
+
|
|
48
|
+
each_srcset_pair(s) do |url, size|
|
|
49
|
+
next if url.start_with?('data:')
|
|
50
|
+
|
|
51
|
+
by_size[size] = url
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# URL token + `Nw`/`Nh` pairs. Commas may appear inside URLs; a candidate
|
|
56
|
+
# boundary is whitespace before a width/height descriptor.
|
|
57
|
+
def each_srcset_pair(string) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
58
|
+
i = 0
|
|
59
|
+
len = string.length
|
|
60
|
+
while i < len
|
|
61
|
+
i += 1 while i < len && sep?(string[i])
|
|
62
|
+
break if i >= len
|
|
63
|
+
|
|
64
|
+
start = i
|
|
65
|
+
i += 1 while i < len && !ws?(string[i])
|
|
66
|
+
url = string[start, i - start]
|
|
67
|
+
|
|
68
|
+
i += 1 while i < len && ws?(string[i])
|
|
69
|
+
size_start = i
|
|
70
|
+
i += 1 while i < len && string[i].between?('0', '9')
|
|
71
|
+
next unless i > size_start && i < len && UNITS.include?(string[i])
|
|
72
|
+
|
|
73
|
+
yield url, string[size_start, i - size_start].to_i
|
|
74
|
+
i += 1
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def style_url(style)
|
|
79
|
+
s = style.to_s
|
|
80
|
+
return if s.length > MAX_ATTR_CHARS
|
|
81
|
+
|
|
82
|
+
pos = s.index('url(')
|
|
83
|
+
return unless pos
|
|
84
|
+
|
|
85
|
+
extract_url_body(s, pos + 4)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def extract_url_body(string, index)
|
|
89
|
+
quote = string[index]
|
|
90
|
+
if QUOTES.include?(quote)
|
|
91
|
+
slice_until(string, index + 1, quote)
|
|
92
|
+
else
|
|
93
|
+
slice_until(string, index, ')')
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def slice_until(string, start, closer)
|
|
98
|
+
close = string.index(closer, start)
|
|
99
|
+
string[start, close - start] if close
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def sep?(char) = ws?(char) || char == ','
|
|
103
|
+
|
|
104
|
+
def ws?(char) = WS.include?(char)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'cgi'
|
|
4
|
-
|
|
5
3
|
module Html2rss
|
|
6
4
|
module Html
|
|
7
5
|
module Rendering
|
|
8
6
|
# Renders an HTML <audio> tag from a URL and type.
|
|
9
7
|
class AudioRenderer
|
|
8
|
+
include EscapedAttributes
|
|
9
|
+
|
|
10
10
|
# @param url [String, Html2rss::Url] media URL for the audio source
|
|
11
11
|
# @param type [String] MIME type for the audio source
|
|
12
12
|
def initialize(url:, type:)
|
|
@@ -22,16 +22,6 @@ module Html2rss
|
|
|
22
22
|
'</audio>'
|
|
23
23
|
].join
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def escaped_url
|
|
29
|
-
CGI.escapeHTML(@url.to_s)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def escaped_type
|
|
33
|
-
CGI.escapeHTML(@type.to_s)
|
|
34
|
-
end
|
|
35
25
|
end
|
|
36
26
|
end
|
|
37
27
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cgi'
|
|
4
|
+
|
|
5
|
+
module Html2rss
|
|
6
|
+
module Html
|
|
7
|
+
module Rendering
|
|
8
|
+
##
|
|
9
|
+
# Shared HTML attribute escaping for media renderers.
|
|
10
|
+
module EscapedAttributes
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def escaped_url
|
|
14
|
+
CGI.escapeHTML(@url.to_s)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def escaped_type
|
|
18
|
+
CGI.escapeHTML(@type.to_s)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def escaped_title
|
|
22
|
+
CGI.escapeHTML(@title.to_s)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'cgi'
|
|
4
|
-
|
|
5
3
|
module Html2rss
|
|
6
4
|
module Html
|
|
7
5
|
module Rendering
|
|
8
6
|
# Renders an HTML <img> tag from a URL and title.
|
|
9
7
|
class ImageRenderer
|
|
8
|
+
include EscapedAttributes
|
|
9
|
+
|
|
10
10
|
# @param url [String, Html2rss::Url] image URL for the src attribute
|
|
11
11
|
# @param title [String, nil] title/alt text for the image
|
|
12
12
|
def initialize(url:, title:)
|
|
@@ -27,16 +27,6 @@ module Html2rss
|
|
|
27
27
|
]
|
|
28
28
|
"<img #{attributes.join(' ')}>"
|
|
29
29
|
end
|
|
30
|
-
|
|
31
|
-
private
|
|
32
|
-
|
|
33
|
-
def escaped_url
|
|
34
|
-
CGI.escapeHTML(@url.to_s)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def escaped_title
|
|
38
|
-
CGI.escapeHTML(@title.to_s)
|
|
39
|
-
end
|
|
40
30
|
end
|
|
41
31
|
end
|
|
42
32
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'cgi'
|
|
4
|
-
|
|
5
3
|
module Html2rss
|
|
6
4
|
module Html
|
|
7
5
|
module Rendering
|
|
8
6
|
# Renders an HTML <iframe> for PDF documents.
|
|
9
7
|
class PdfRenderer
|
|
8
|
+
include EscapedAttributes
|
|
9
|
+
|
|
10
10
|
# @param url [String, Html2rss::Url] PDF URL rendered in the iframe
|
|
11
11
|
def initialize(url:)
|
|
12
12
|
@url = url
|
|
@@ -24,12 +24,6 @@ module Html2rss
|
|
|
24
24
|
]
|
|
25
25
|
"<iframe #{attributes.join(' ')}></iframe>"
|
|
26
26
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def escaped_url
|
|
31
|
-
CGI.escapeHTML(@url.to_s)
|
|
32
|
-
end
|
|
33
27
|
end
|
|
34
28
|
end
|
|
35
29
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'cgi'
|
|
4
|
-
|
|
5
3
|
module Html2rss
|
|
6
4
|
module Html
|
|
7
5
|
module Rendering
|
|
8
6
|
# Renders an HTML <video> tag from a URL and type.
|
|
9
7
|
class VideoRenderer
|
|
8
|
+
include EscapedAttributes
|
|
9
|
+
|
|
10
10
|
# @param url [String, Html2rss::Url] media URL for the video source
|
|
11
11
|
# @param type [String] MIME type for the video source
|
|
12
12
|
def initialize(url:, type:)
|
|
@@ -22,16 +22,6 @@ module Html2rss
|
|
|
22
22
|
'</video>'
|
|
23
23
|
].join
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def escaped_url
|
|
29
|
-
CGI.escapeHTML(@url.to_s)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def escaped_type
|
|
33
|
-
CGI.escapeHTML(@type.to_s)
|
|
34
|
-
end
|
|
35
25
|
end
|
|
36
26
|
end
|
|
37
27
|
end
|