html2rss 0.21.0 → 0.22.1

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/html2rss/auto_source/scraper/html/class_clustering.rb +196 -0
  3. data/lib/html2rss/auto_source/scraper/html.rb +14 -2
  4. data/lib/html2rss/auto_source/scraper/json_state.rb +1 -1
  5. data/lib/html2rss/auto_source/scraper/microdata.rb +16 -4
  6. data/lib/html2rss/auto_source/scraper/semantic_html/deduplicator.rb +1 -1
  7. data/lib/html2rss/auto_source/scraper/semantic_html.rb +28 -20
  8. data/lib/html2rss/auto_source.rb +6 -2
  9. data/lib/html2rss/cli.rb +61 -10
  10. data/lib/html2rss/config/class_methods.rb +3 -3
  11. data/lib/html2rss/config/validator.rb +1 -0
  12. data/lib/html2rss/config.rb +2 -2
  13. data/lib/html2rss/html_extractor/heading_extractor.rb +50 -0
  14. data/lib/html2rss/html_extractor/id_generator.rb +67 -0
  15. data/lib/html2rss/html_extractor/semantic_anchor_candidates.rb +4 -18
  16. data/lib/html2rss/html_extractor/semantic_containers.rb +28 -3
  17. data/lib/html2rss/html_extractor/text_extractor.rb +77 -0
  18. data/lib/html2rss/html_extractor.rb +78 -61
  19. data/lib/html2rss/html_navigator.rb +17 -0
  20. data/lib/html2rss/rendering.rb +2 -2
  21. data/lib/html2rss/request_service/local_file_strategy.rb +29 -0
  22. data/lib/html2rss/request_service/puppet_commander.rb +4 -0
  23. data/lib/html2rss/request_service.rb +2 -1
  24. data/lib/html2rss/selectors/extractors/attribute.rb +1 -1
  25. data/lib/html2rss/selectors/extractors/href.rb +1 -1
  26. data/lib/html2rss/selectors/extractors/html.rb +1 -1
  27. data/lib/html2rss/selectors/extractors/static.rb +1 -1
  28. data/lib/html2rss/selectors/extractors/text.rb +1 -1
  29. data/lib/html2rss/selectors/post_processors/sanitize_html.rb +8 -1
  30. data/lib/html2rss/selectors.rb +9 -4
  31. data/lib/html2rss/url.rb +26 -13
  32. data/lib/html2rss/version.rb +1 -1
  33. data/lib/html2rss.rb +28 -6
  34. data/schema/html2rss-config.schema.json +20 -2
  35. metadata +7 -2
data/lib/html2rss/url.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'addressable/uri'
4
4
  require 'cgi'
5
+ require 'nokogiri'
5
6
 
6
7
  module Html2rss
7
8
  ##
@@ -55,10 +56,9 @@ module Html2rss
55
56
  # @return [Url, nil] the sanitized URL, or nil if no valid URL found
56
57
  def self.sanitize(raw_url)
57
58
  match = raw_url.to_s.match(%r{(?:(?:https?|ftp|mailto)://|mailto:)[^\s<>"]+})
58
- url = match ? match[0].strip : ''
59
- return nil if url.empty?
59
+ return unless match
60
60
 
61
- new(Addressable::URI.parse(url).normalize)
61
+ new(Addressable::URI.parse(match[0].strip).normalize)
62
62
  end
63
63
 
64
64
  ##
@@ -70,10 +70,9 @@ module Html2rss
70
70
  def self.from_absolute(url_string)
71
71
  return url_string if url_string.is_a?(self)
72
72
 
73
- url = new(Addressable::URI.parse(url_string.to_s.strip).normalize)
74
- raise ArgumentError, 'URL must be absolute' unless url.absolute?
75
-
76
- url
73
+ new(Addressable::URI.parse(url_string.to_s.strip).normalize).tap do |url|
74
+ raise ArgumentError, 'URL must be absolute' unless url.absolute?
75
+ end
77
76
  rescue Addressable::URI::InvalidURIError
78
77
  raise ArgumentError, 'URL must be absolute'
79
78
  end
@@ -92,14 +91,28 @@ module Html2rss
92
91
  # Url.for_channel('/relative/path')
93
92
  # # => raises ArgumentError: "URL must be absolute"
94
93
  def self.for_channel(url_string)
95
- return nil if url_string.nil? || url_string.empty?
96
-
97
- stripped = url_string.strip
94
+ stripped = url_string.to_s.strip
98
95
  return nil if stripped.empty?
99
96
 
100
- url = from_absolute(stripped)
101
- validate_channel_url(url)
102
- url
97
+ from_absolute(stripped).tap { validate_channel_url(_1) }
98
+ end
99
+
100
+ ##
101
+ # Extracts a base URL from HTML metadata tags.
102
+ #
103
+ # @param html [String] raw HTML content
104
+ # @return [Url, nil] the extracted absolute URL, or nil if none is found
105
+ def self.extract_from_html(html)
106
+ doc = Nokogiri::HTML(html)
107
+ tags = { 'link[rel="canonical"]' => 'href', 'meta[property="og:url"]' => 'content',
108
+ 'meta[name="twitter:url"]' => 'content', 'base[href]' => 'href' }
109
+ tags.each do |sel, attr|
110
+ val = doc.at_css(sel)&.[](attr).to_s.strip
111
+ return from_absolute(val) unless val.empty?
112
+ rescue ArgumentError
113
+ next
114
+ end
115
+ nil
103
116
  end
104
117
 
105
118
  ##
@@ -4,6 +4,6 @@
4
4
  # The Html2rss namespace.
5
5
  module Html2rss
6
6
  # Current application version.
7
- VERSION = '0.21.0'
7
+ VERSION = '0.22.1'
8
8
  public_constant :VERSION
9
9
  end
data/lib/html2rss.rb CHANGED
@@ -52,6 +52,8 @@ module Html2rss
52
52
  FeedPipeline.new(raw_config).to_json_feed
53
53
  end
54
54
 
55
+ # rubocop:disable Metrics/ParameterLists
56
+
55
57
  ##
56
58
  # Scrapes the provided URL and returns an RSS object.
57
59
  #
@@ -60,9 +62,15 @@ module Html2rss
60
62
  # @param items_selector [String, nil] optional selector hint for item extraction
61
63
  # @param max_redirects [Integer, nil] optional redirect limit override
62
64
  # @param max_requests [Integer, nil] optional request budget override
65
+ # @param local_file_path [String, nil] optional local HTML file path
63
66
  # @return [RSS::Rss] generated RSS feed
64
- def self.auto_source(url, strategy: :auto, items_selector: nil, max_redirects: nil, max_requests: nil)
65
- feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:))
67
+ def self.auto_source(url,
68
+ strategy: :auto,
69
+ items_selector: nil,
70
+ max_redirects: nil,
71
+ max_requests: nil,
72
+ local_file_path: nil)
73
+ feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:, local_file_path:))
66
74
  end
67
75
 
68
76
  ##
@@ -73,11 +81,20 @@ module Html2rss
73
81
  # @param items_selector [String, nil] optional selector hint for item extraction
74
82
  # @param max_redirects [Integer, nil] optional redirect limit override
75
83
  # @param max_requests [Integer, nil] optional request budget override
84
+ # @param local_file_path [String, nil] optional local HTML file path
76
85
  # @return [Hash] JSONFeed-compliant hash
77
- def self.auto_json_feed(url, strategy: :auto, items_selector: nil, max_redirects: nil, max_requests: nil)
78
- json_feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:))
86
+ def self.auto_json_feed(url,
87
+ strategy: :auto,
88
+ items_selector: nil,
89
+ max_redirects: nil,
90
+ max_requests: nil,
91
+ local_file_path: nil)
92
+ json_feed(build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:,
93
+ local_file_path:))
79
94
  end
80
95
 
96
+ # rubocop:enable Metrics/ParameterLists
97
+
81
98
  # rubocop:disable ThreadSafety/ClassInstanceVariable
82
99
  class << self
83
100
  ##
@@ -125,12 +142,17 @@ module Html2rss
125
142
  class << self
126
143
  private
127
144
 
128
- def build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:)
129
- Config.auto_source_config(
145
+ def build_auto_source_config(url:, strategy:, items_selector:, max_redirects:, max_requests:, local_file_path: nil) # rubocop:disable Metrics/ParameterLists
146
+ config = Config.auto_source_config(
130
147
  url:,
131
148
  items_selector:,
132
149
  request_controls: shortcut_request_controls(strategy:, max_redirects:, max_requests:)
133
150
  )
151
+ if local_file_path
152
+ config[:request] ||= {}
153
+ config[:request][:local_file_path] = local_file_path
154
+ end
155
+ config
134
156
  end
135
157
 
136
158
  def shortcut_request_controls(strategy:, max_redirects:, max_requests:)
@@ -153,6 +153,12 @@
153
153
  "not": {
154
154
  "type": "null"
155
155
  }
156
+ },
157
+ "fallback_anchorless": {
158
+ "type": "boolean",
159
+ "not": {
160
+ "type": "null"
161
+ }
156
162
  }
157
163
  },
158
164
  "required": []
@@ -179,6 +185,12 @@
179
185
  "type": "null"
180
186
  },
181
187
  "exclusiveMinimum": 0
188
+ },
189
+ "fallback_anchorless": {
190
+ "type": "boolean",
191
+ "not": {
192
+ "type": "null"
193
+ }
182
194
  }
183
195
  },
184
196
  "required": []
@@ -222,12 +234,14 @@
222
234
  "enabled": true
223
235
  },
224
236
  "semantic_html": {
225
- "enabled": true
237
+ "enabled": true,
238
+ "fallback_anchorless": true
226
239
  },
227
240
  "html": {
228
241
  "enabled": true,
229
242
  "minimum_selector_frequency": 2,
230
- "use_top_selectors": 5
243
+ "use_top_selectors": 5,
244
+ "fallback_anchorless": true
231
245
  }
232
246
  },
233
247
  "cleanup": {
@@ -535,6 +549,10 @@
535
549
  }
536
550
  },
537
551
  "required": []
552
+ },
553
+ "local_file_path": {
554
+ "type": "string",
555
+ "minLength": 1
538
556
  }
539
557
  },
540
558
  "required": []
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: html2rss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.21.0
4
+ version: 0.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gil Desmarais
@@ -278,6 +278,7 @@ files:
278
278
  - lib/html2rss/auto_source/cleanup.rb
279
279
  - lib/html2rss/auto_source/scraper.rb
280
280
  - lib/html2rss/auto_source/scraper/html.rb
281
+ - lib/html2rss/auto_source/scraper/html/class_clustering.rb
281
282
  - lib/html2rss/auto_source/scraper/json_state.rb
282
283
  - lib/html2rss/auto_source/scraper/link_heuristics.rb
283
284
  - lib/html2rss/auto_source/scraper/microdata.rb
@@ -310,10 +311,13 @@ files:
310
311
  - lib/html2rss/html_extractor.rb
311
312
  - lib/html2rss/html_extractor/date_extractor.rb
312
313
  - lib/html2rss/html_extractor/enclosure_extractor.rb
314
+ - lib/html2rss/html_extractor/heading_extractor.rb
315
+ - lib/html2rss/html_extractor/id_generator.rb
313
316
  - lib/html2rss/html_extractor/image_extractor.rb
314
317
  - lib/html2rss/html_extractor/list_candidates.rb
315
318
  - lib/html2rss/html_extractor/semantic_anchor_candidates.rb
316
319
  - lib/html2rss/html_extractor/semantic_containers.rb
320
+ - lib/html2rss/html_extractor/text_extractor.rb
317
321
  - lib/html2rss/html_navigator.rb
318
322
  - lib/html2rss/json_feed_builder.rb
319
323
  - lib/html2rss/json_feed_builder/item.rb
@@ -332,6 +336,7 @@ files:
332
336
  - lib/html2rss/request_service/budget.rb
333
337
  - lib/html2rss/request_service/context.rb
334
338
  - lib/html2rss/request_service/faraday_strategy.rb
339
+ - lib/html2rss/request_service/local_file_strategy.rb
335
340
  - lib/html2rss/request_service/policy.rb
336
341
  - lib/html2rss/request_service/puppet_commander.rb
337
342
  - lib/html2rss/request_service/response.rb
@@ -376,7 +381,7 @@ licenses:
376
381
  - MIT
377
382
  metadata:
378
383
  allowed_push_host: https://rubygems.org
379
- changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.21.0
384
+ changelog_uri: https://github.com/html2rss/html2rss/releases/tag/v0.22.1
380
385
  rubygems_mfa_required: 'true'
381
386
  rdoc_options: []
382
387
  require_paths: