html2rss 0.24.0 → 0.26.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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -2
  3. data/html2rss.gemspec +4 -1
  4. data/lib/html2rss/auto_source/cleanup.rb +62 -36
  5. data/lib/html2rss/auto_source/scraper/html.rb +68 -145
  6. data/lib/html2rss/auto_source/scraper/json_state/article_normalizer.rb +116 -0
  7. data/lib/html2rss/auto_source/scraper/json_state/candidate_detector.rb +63 -0
  8. data/lib/html2rss/auto_source/scraper/json_state/document_scanner.rb +179 -0
  9. data/lib/html2rss/auto_source/scraper/json_state/value_finder.rb +55 -0
  10. data/lib/html2rss/auto_source/scraper/json_state.rb +31 -401
  11. data/lib/html2rss/auto_source/scraper/schema/item_list.rb +2 -14
  12. data/lib/html2rss/auto_source/scraper/semantic_html/entry_deduplicator.rb +35 -30
  13. data/lib/html2rss/auto_source/scraper/semantic_html.rb +40 -120
  14. data/lib/html2rss/auto_source/scraper/sitemap/parser.rb +161 -0
  15. data/lib/html2rss/auto_source/scraper/sitemap.rb +10 -7
  16. data/lib/html2rss/auto_source/scraper/wordpress_api.rb +6 -2
  17. data/lib/html2rss/auto_source/scraper/xhr_articles.rb +69 -0
  18. data/lib/html2rss/auto_source/scraper.rb +109 -53
  19. data/lib/html2rss/auto_source/segment.rb +29 -0
  20. data/lib/html2rss/auto_source/segmenter/cluster.rb +163 -0
  21. data/lib/html2rss/auto_source/segmenter/list.rb +103 -0
  22. data/lib/html2rss/auto_source/segmenter/primary_link.rb +80 -0
  23. data/lib/html2rss/auto_source/segmenter/semantic.rb +104 -0
  24. data/lib/html2rss/auto_source/segmenter.rb +95 -0
  25. data/lib/html2rss/auto_source.rb +79 -21
  26. data/lib/html2rss/capture.rb +315 -0
  27. data/lib/html2rss/cli.rb +72 -18
  28. data/lib/html2rss/config/auto_source_contract.rb +5 -1
  29. data/lib/html2rss/config/request_controls.rb +33 -0
  30. data/lib/html2rss/config/schema.rb +67 -26
  31. data/lib/html2rss/config/selectors_validator.rb +60 -21
  32. data/lib/html2rss/config/validator.rb +33 -35
  33. data/lib/html2rss/config.rb +6 -2
  34. data/lib/html2rss/error.rb +15 -3
  35. data/lib/html2rss/feed_builder/rss.rb +17 -5
  36. data/lib/html2rss/feed_pipeline/auto_fallback.rb +16 -6
  37. data/lib/html2rss/feed_pipeline/runtime_policy.rb +3 -43
  38. data/lib/html2rss/feed_pipeline.rb +11 -0
  39. data/lib/html2rss/feed_result.rb +1 -1
  40. data/lib/html2rss/hash_util.rb +17 -0
  41. data/lib/html2rss/html/article_extractor/category_extractor.rb +10 -36
  42. data/lib/html2rss/html/article_extractor/date_extractor.rb +2 -7
  43. data/lib/html2rss/html/article_extractor/enclosure_extractor.rb +5 -50
  44. data/lib/html2rss/html/article_extractor/image_extractor.rb +5 -16
  45. data/lib/html2rss/html/article_rules/category.rb +55 -0
  46. data/lib/html2rss/html/article_rules/date.rb +25 -0
  47. data/lib/html2rss/html/article_rules/enclosure.rb +72 -0
  48. data/lib/html2rss/html/article_rules/image.rb +109 -0
  49. data/lib/html2rss/html/article_rules.rb +10 -0
  50. data/lib/html2rss/html/rendering/audio_renderer.rb +2 -12
  51. data/lib/html2rss/html/rendering/escaped_attributes.rb +27 -0
  52. data/lib/html2rss/html/rendering/image_renderer.rb +2 -12
  53. data/lib/html2rss/html/rendering/pdf_renderer.rb +2 -8
  54. data/lib/html2rss/html/rendering/video_renderer.rb +2 -12
  55. data/lib/html2rss/html/sst_article_extractor.rb +308 -0
  56. data/lib/html2rss/link_destination/destination_facts.rb +40 -0
  57. data/lib/html2rss/link_destination/noise_policy.rb +68 -0
  58. data/lib/html2rss/link_destination/path_classifier.rb +208 -0
  59. data/lib/html2rss/link_destination/text_classifier.rb +64 -0
  60. data/lib/html2rss/link_destination.rb +8 -0
  61. data/lib/html2rss/mcp/server.rb +577 -0
  62. data/lib/html2rss/mcp.rb +21 -0
  63. data/lib/html2rss/request_service/blocked_surface.rb +24 -1
  64. data/lib/html2rss/request_service/botasaurus_contract.rb +89 -9
  65. data/lib/html2rss/request_service/botasaurus_strategy.rb +4 -2
  66. data/lib/html2rss/request_service/budget.rb +7 -35
  67. data/lib/html2rss/request_service/context.rb +0 -6
  68. data/lib/html2rss/request_service/faraday_strategy.rb +87 -3
  69. data/lib/html2rss/request_service/network_guard.rb +5 -3
  70. data/lib/html2rss/request_service/policy.rb +1 -1
  71. data/lib/html2rss/request_service/response.rb +15 -1
  72. data/lib/html2rss/request_service/strategy.rb +1 -2
  73. data/lib/html2rss/request_service.rb +4 -9
  74. data/lib/html2rss/scoring/cluster_scorer.rb +87 -0
  75. data/lib/html2rss/scoring/container_assessor.rb +77 -0
  76. data/lib/html2rss/scoring/engine.rb +145 -0
  77. data/lib/html2rss/scoring/link_resolver.rb +84 -0
  78. data/lib/html2rss/scoring/observation.rb +53 -0
  79. data/lib/html2rss/scoring/ranked_segment.rb +33 -0
  80. data/lib/html2rss/scoring/score.rb +11 -0
  81. data/lib/html2rss/scoring.rb +8 -0
  82. data/lib/html2rss/selectors/extractors/attribute.rb +15 -0
  83. data/lib/html2rss/selectors/extractors/href.rb +12 -0
  84. data/lib/html2rss/selectors/extractors/html.rb +12 -0
  85. data/lib/html2rss/selectors/extractors/static.rb +14 -0
  86. data/lib/html2rss/selectors/extractors/text.rb +11 -0
  87. data/lib/html2rss/selectors/post_processors/gsub.rb +18 -0
  88. data/lib/html2rss/selectors/post_processors/html_to_markdown.rb +11 -0
  89. data/lib/html2rss/selectors/post_processors/markdown_to_html.rb +12 -0
  90. data/lib/html2rss/selectors/post_processors/parse_time.rb +11 -0
  91. data/lib/html2rss/selectors/post_processors/parse_uri.rb +11 -0
  92. data/lib/html2rss/selectors/post_processors/sanitize_html.rb +23 -1
  93. data/lib/html2rss/selectors/post_processors/substring.rb +21 -0
  94. data/lib/html2rss/selectors/post_processors/template.rb +20 -0
  95. data/lib/html2rss/selectors/schema_doc.rb +99 -0
  96. data/lib/html2rss/selectors.rb +0 -20
  97. data/lib/html2rss/sst/attrs.rb +91 -0
  98. data/lib/html2rss/sst/document.rb +23 -0
  99. data/lib/html2rss/sst/index.rb +112 -0
  100. data/lib/html2rss/sst/node.rb +147 -0
  101. data/lib/html2rss/sst/normalizer.rb +171 -0
  102. data/lib/html2rss/sst/tags.rb +26 -0
  103. data/lib/html2rss/sst/text.rb +81 -0
  104. data/lib/html2rss/sst.rb +8 -0
  105. data/lib/html2rss/url.rb +11 -7
  106. data/lib/html2rss/version.rb +1 -1
  107. data/lib/html2rss.rb +57 -26
  108. data/schema/html2rss-config.schema.json +428 -85
  109. metadata +93 -27
  110. data/lib/html2rss/auto_source/discovery/dom_clustering/group_scorer.rb +0 -80
  111. data/lib/html2rss/auto_source/discovery/dom_clustering/overlap_resolver.rb +0 -86
  112. data/lib/html2rss/auto_source/discovery/dom_clustering.rb +0 -119
  113. data/lib/html2rss/auto_source/discovery/list_candidates.rb +0 -94
  114. data/lib/html2rss/auto_source/discovery/semantic_anchor_candidates.rb +0 -219
  115. data/lib/html2rss/auto_source/discovery/semantic_containers.rb +0 -71
  116. data/lib/html2rss/auto_source/discovery/sitemap.rb +0 -159
  117. data/lib/html2rss/auto_source/discovery.rb +0 -14
  118. data/lib/html2rss/auto_source/link_heuristics/anchor_signals.rb +0 -28
  119. data/lib/html2rss/auto_source/link_heuristics/container_assessor.rb +0 -106
  120. data/lib/html2rss/auto_source/link_heuristics/container_signals.rb +0 -80
  121. data/lib/html2rss/auto_source/link_heuristics/destination_facts.rb +0 -42
  122. data/lib/html2rss/auto_source/link_heuristics/href_extractor.rb +0 -38
  123. data/lib/html2rss/auto_source/link_heuristics/path_classifier.rb +0 -221
  124. data/lib/html2rss/auto_source/link_heuristics/text_classifier.rb +0 -66
  125. data/lib/html2rss/auto_source/link_heuristics.rb +0 -139
  126. data/lib/html2rss/request_service/browserless_strategy.rb +0 -132
  127. data/lib/html2rss/request_service/puppet_commander/navigation_guards.rb +0 -148
  128. data/lib/html2rss/request_service/puppet_commander/preload_runner.rb +0 -86
  129. data/lib/html2rss/request_service/puppet_commander.rb +0 -95
@@ -45,12 +45,55 @@
45
45
  "null",
46
46
  "string"
47
47
  ]
48
+ },
49
+ "author": {
50
+ "type": [
51
+ "null",
52
+ "string"
53
+ ]
54
+ },
55
+ "image": {
56
+ "type": [
57
+ "null",
58
+ "string"
59
+ ]
48
60
  }
49
61
  },
50
62
  "required": [
51
63
  "url"
52
64
  ]
53
65
  },
66
+ "directory": {
67
+ "type": "object",
68
+ "properties": {
69
+ "topics": {
70
+ "type": "array",
71
+ "items": {
72
+ "minLength": 1,
73
+ "type": "string",
74
+ "enum": [
75
+ "sports",
76
+ "energy",
77
+ "tech",
78
+ "science",
79
+ "news",
80
+ "entertainment",
81
+ "jobs",
82
+ "finance",
83
+ "security",
84
+ "travel",
85
+ "environment",
86
+ "consumer",
87
+ "civic",
88
+ "product",
89
+ "research"
90
+ ]
91
+ },
92
+ "minItems": 1
93
+ }
94
+ },
95
+ "required": []
96
+ },
54
97
  "headers": {
55
98
  "type": "object",
56
99
  "description": "HTTP headers applied to every request.",
@@ -94,6 +137,13 @@
94
137
  "$schema": "http://json-schema.org/draft-06/schema#",
95
138
  "type": "object",
96
139
  "properties": {
140
+ "limit": {
141
+ "type": "integer",
142
+ "not": {
143
+ "type": "null"
144
+ },
145
+ "exclusiveMinimum": 0
146
+ },
97
147
  "scraper": {
98
148
  "type": "object",
99
149
  "properties": {
@@ -182,6 +232,18 @@
182
232
  },
183
233
  "required": []
184
234
  },
235
+ "xhr_articles": {
236
+ "type": "object",
237
+ "properties": {
238
+ "enabled": {
239
+ "type": "boolean",
240
+ "not": {
241
+ "type": "null"
242
+ }
243
+ }
244
+ },
245
+ "required": []
246
+ },
185
247
  "meta_oembed": {
186
248
  "type": "object",
187
249
  "properties": {
@@ -255,13 +317,6 @@
255
317
  "not": {
256
318
  "type": "null"
257
319
  }
258
- },
259
- "min_words_title": {
260
- "type": "integer",
261
- "not": {
262
- "type": "null"
263
- },
264
- "exclusiveMinimum": 0
265
320
  }
266
321
  },
267
322
  "required": []
@@ -269,6 +324,7 @@
269
324
  },
270
325
  "required": [],
271
326
  "default": {
327
+ "limit": 25,
272
328
  "scraper": {
273
329
  "wordpress_api": {
274
330
  "enabled": true
@@ -290,6 +346,9 @@
290
346
  "json_state": {
291
347
  "enabled": true
292
348
  },
349
+ "xhr_articles": {
350
+ "enabled": true
351
+ },
293
352
  "meta_oembed": {
294
353
  "enabled": true
295
354
  },
@@ -305,8 +364,7 @@
305
364
  }
306
365
  },
307
366
  "cleanup": {
308
- "keep_different_domain": false,
309
- "min_words_title": 3
367
+ "keep_different_domain": false
310
368
  }
311
369
  }
312
370
  },
@@ -402,8 +460,24 @@
402
460
  "type": "object",
403
461
  "properties": {
404
462
  "extractor": {
405
- "type": "string",
406
- "minLength": 1
463
+ "description": "Extractor used to pull a value from the selected element.",
464
+ "oneOf": [
465
+ {
466
+ "$ref": "#/$defs/extractors/attribute"
467
+ },
468
+ {
469
+ "$ref": "#/$defs/extractors/href"
470
+ },
471
+ {
472
+ "$ref": "#/$defs/extractors/html"
473
+ },
474
+ {
475
+ "$ref": "#/$defs/extractors/static"
476
+ },
477
+ {
478
+ "$ref": "#/$defs/extractors/text"
479
+ }
480
+ ]
407
481
  },
408
482
  "attribute": {
409
483
  "type": "string",
@@ -415,8 +489,34 @@
415
489
  },
416
490
  "post_process": {
417
491
  "type": "array",
492
+ "description": "Ordered transforms applied to the extracted value.",
418
493
  "items": {
419
- "type": "object"
494
+ "oneOf": [
495
+ {
496
+ "$ref": "#/$defs/post_processors/gsub"
497
+ },
498
+ {
499
+ "$ref": "#/$defs/post_processors/html_to_markdown"
500
+ },
501
+ {
502
+ "$ref": "#/$defs/post_processors/markdown_to_html"
503
+ },
504
+ {
505
+ "$ref": "#/$defs/post_processors/parse_time"
506
+ },
507
+ {
508
+ "$ref": "#/$defs/post_processors/parse_uri"
509
+ },
510
+ {
511
+ "$ref": "#/$defs/post_processors/sanitize_html"
512
+ },
513
+ {
514
+ "$ref": "#/$defs/post_processors/substring"
515
+ },
516
+ {
517
+ "$ref": "#/$defs/post_processors/template"
518
+ }
519
+ ]
420
520
  }
421
521
  },
422
522
  "content_type": {
@@ -452,8 +552,24 @@
452
552
  "type": "object",
453
553
  "properties": {
454
554
  "extractor": {
455
- "type": "string",
456
- "minLength": 1
555
+ "description": "Extractor used to pull a value from the selected element.",
556
+ "oneOf": [
557
+ {
558
+ "$ref": "#/$defs/extractors/attribute"
559
+ },
560
+ {
561
+ "$ref": "#/$defs/extractors/href"
562
+ },
563
+ {
564
+ "$ref": "#/$defs/extractors/html"
565
+ },
566
+ {
567
+ "$ref": "#/$defs/extractors/static"
568
+ },
569
+ {
570
+ "$ref": "#/$defs/extractors/text"
571
+ }
572
+ ]
457
573
  },
458
574
  "attribute": {
459
575
  "type": "string",
@@ -465,8 +581,34 @@
465
581
  },
466
582
  "post_process": {
467
583
  "type": "array",
584
+ "description": "Ordered transforms applied to the extracted value.",
468
585
  "items": {
469
- "type": "object"
586
+ "oneOf": [
587
+ {
588
+ "$ref": "#/$defs/post_processors/gsub"
589
+ },
590
+ {
591
+ "$ref": "#/$defs/post_processors/html_to_markdown"
592
+ },
593
+ {
594
+ "$ref": "#/$defs/post_processors/markdown_to_html"
595
+ },
596
+ {
597
+ "$ref": "#/$defs/post_processors/parse_time"
598
+ },
599
+ {
600
+ "$ref": "#/$defs/post_processors/parse_uri"
601
+ },
602
+ {
603
+ "$ref": "#/$defs/post_processors/sanitize_html"
604
+ },
605
+ {
606
+ "$ref": "#/$defs/post_processors/substring"
607
+ },
608
+ {
609
+ "$ref": "#/$defs/post_processors/template"
610
+ }
611
+ ]
470
612
  }
471
613
  }
472
614
  },
@@ -500,77 +642,18 @@
500
642
  },
501
643
  "exclusiveMinimum": 0
502
644
  },
503
- "browserless": {
504
- "type": "object",
505
- "properties": {
506
- "preload": {
507
- "type": "object",
508
- "properties": {
509
- "wait_after_ms": {
510
- "type": "integer",
511
- "not": {
512
- "type": "null"
513
- },
514
- "minimum": 0
515
- },
516
- "click_selectors": {
517
- "type": "array",
518
- "items": {
519
- "type": "object",
520
- "properties": {
521
- "selector": {
522
- "type": "string",
523
- "minLength": 1
524
- },
525
- "max_clicks": {
526
- "type": "integer",
527
- "not": {
528
- "type": "null"
529
- },
530
- "exclusiveMinimum": 0
531
- },
532
- "wait_after_ms": {
533
- "type": "integer",
534
- "not": {
535
- "type": "null"
536
- },
537
- "minimum": 0
538
- }
539
- },
540
- "required": [
541
- "selector"
542
- ]
543
- }
544
- },
545
- "scroll_down": {
546
- "type": "object",
547
- "properties": {
548
- "iterations": {
549
- "type": "integer",
550
- "not": {
551
- "type": "null"
552
- },
553
- "exclusiveMinimum": 0
554
- },
555
- "wait_after_ms": {
556
- "type": "integer",
557
- "not": {
558
- "type": "null"
559
- },
560
- "minimum": 0
561
- }
562
- },
563
- "required": []
564
- }
565
- },
566
- "required": []
567
- }
568
- },
569
- "required": []
570
- },
571
645
  "botasaurus": {
572
646
  "type": "object",
573
647
  "properties": {
648
+ "execution_mode": {
649
+ "type": "string",
650
+ "minLength": 1,
651
+ "enum": [
652
+ "auto",
653
+ "request",
654
+ "browser"
655
+ ]
656
+ },
574
657
  "navigation_mode": {
575
658
  "type": "string",
576
659
  "minLength": 1,
@@ -578,7 +661,8 @@
578
661
  "auto",
579
662
  "get",
580
663
  "google_get",
581
- "google_get_bypass"
664
+ "google_get_bypass",
665
+ "organic_get"
582
666
  ]
583
667
  },
584
668
  "max_retries": {
@@ -602,6 +686,18 @@
602
686
  },
603
687
  "exclusiveMinimum": 0
604
688
  },
689
+ "scroll": {
690
+ "type": "boolean",
691
+ "not": {
692
+ "type": "null"
693
+ }
694
+ },
695
+ "scroll_to_bottom": {
696
+ "type": "boolean",
697
+ "not": {
698
+ "type": "null"
699
+ }
700
+ },
605
701
  "block_images": {
606
702
  "type": "boolean",
607
703
  "not": {
@@ -614,6 +710,12 @@
614
710
  "type": "null"
615
711
  }
616
712
  },
713
+ "block_trackers": {
714
+ "type": "boolean",
715
+ "not": {
716
+ "type": "null"
717
+ }
718
+ },
617
719
  "wait_for_complete_page_load": {
618
720
  "type": "boolean",
619
721
  "not": {
@@ -646,6 +748,12 @@
646
748
  "lang": {
647
749
  "type": "string",
648
750
  "minLength": 1
751
+ },
752
+ "cookies": {
753
+ "type": "object"
754
+ },
755
+ "headers": {
756
+ "type": "object"
649
757
  }
650
758
  },
651
759
  "required": []
@@ -672,5 +780,240 @@
672
780
  "auto_source"
673
781
  ]
674
782
  }
675
- ]
783
+ ],
784
+ "$defs": {
785
+ "post_processors": {
786
+ "gsub": {
787
+ "type": "object",
788
+ "title": "gsub",
789
+ "description": "Replace matches of `pattern` in the extracted string with `replacement` (Ruby String#gsub; pattern may be a regexp-like string).",
790
+ "examples": [
791
+ {
792
+ "name": "gsub",
793
+ "pattern": "boo",
794
+ "replacement": "baz"
795
+ }
796
+ ],
797
+ "properties": {
798
+ "name": {
799
+ "type": "string",
800
+ "const": "gsub"
801
+ },
802
+ "pattern": {
803
+ "type": "string"
804
+ },
805
+ "replacement": {
806
+ "type": "string"
807
+ }
808
+ },
809
+ "required": [
810
+ "name",
811
+ "pattern",
812
+ "replacement"
813
+ ],
814
+ "additionalProperties": true
815
+ },
816
+ "html_to_markdown": {
817
+ "type": "object",
818
+ "title": "html_to_markdown",
819
+ "description": "Sanitize HTML then convert it to Markdown (via ReverseMarkdown).",
820
+ "examples": [
821
+ {
822
+ "name": "html_to_markdown"
823
+ }
824
+ ],
825
+ "properties": {
826
+ "name": {
827
+ "type": "string",
828
+ "const": "html_to_markdown"
829
+ }
830
+ },
831
+ "required": [
832
+ "name"
833
+ ],
834
+ "additionalProperties": true
835
+ },
836
+ "markdown_to_html": {
837
+ "type": "object",
838
+ "title": "markdown_to_html",
839
+ "description": "Convert Markdown to HTML (Kramdown) and sanitize the result. Often chained after `template`.",
840
+ "examples": [
841
+ {
842
+ "name": "markdown_to_html"
843
+ }
844
+ ],
845
+ "properties": {
846
+ "name": {
847
+ "type": "string",
848
+ "const": "markdown_to_html"
849
+ }
850
+ },
851
+ "required": [
852
+ "name"
853
+ ],
854
+ "additionalProperties": true
855
+ },
856
+ "parse_time": {
857
+ "type": "object",
858
+ "title": "parse_time",
859
+ "description": "Parse a time string with Time.parse and return RFC822, using the channel `time_zone`.",
860
+ "examples": [
861
+ {
862
+ "name": "parse_time"
863
+ }
864
+ ],
865
+ "properties": {
866
+ "name": {
867
+ "type": "string",
868
+ "const": "parse_time"
869
+ }
870
+ },
871
+ "required": [
872
+ "name"
873
+ ],
874
+ "additionalProperties": true
875
+ },
876
+ "parse_uri": {
877
+ "type": "object",
878
+ "title": "parse_uri",
879
+ "description": "Normalize a URL string; resolve relative URLs against the channel URL.",
880
+ "examples": [
881
+ {
882
+ "name": "parse_uri"
883
+ }
884
+ ],
885
+ "properties": {
886
+ "name": {
887
+ "type": "string",
888
+ "const": "parse_uri"
889
+ }
890
+ },
891
+ "required": [
892
+ "name"
893
+ ],
894
+ "additionalProperties": true
895
+ },
896
+ "sanitize_html": {
897
+ "type": "object",
898
+ "title": "sanitize_html",
899
+ "description": "Sanitize HTML (sanitize gem RELAXED plus html2rss defaults: absolute URLs, safe link/img attributes, wrap lone images in anchors).",
900
+ "examples": [
901
+ {
902
+ "name": "sanitize_html"
903
+ }
904
+ ],
905
+ "properties": {
906
+ "name": {
907
+ "type": "string",
908
+ "const": "sanitize_html"
909
+ }
910
+ },
911
+ "required": [
912
+ "name"
913
+ ],
914
+ "additionalProperties": true
915
+ },
916
+ "substring": {
917
+ "type": "object",
918
+ "title": "substring",
919
+ "description": "Return a slice of the extracted string using Integer `start` and optional `end` (Ruby String#[] range semantics; end may be omitted).",
920
+ "examples": [
921
+ {
922
+ "name": "substring",
923
+ "start": 4,
924
+ "end": 6
925
+ }
926
+ ],
927
+ "properties": {
928
+ "name": {
929
+ "type": "string",
930
+ "const": "substring"
931
+ },
932
+ "start": {
933
+ "type": "integer"
934
+ },
935
+ "end": {
936
+ "type": "integer"
937
+ }
938
+ },
939
+ "required": [
940
+ "name",
941
+ "start"
942
+ ],
943
+ "additionalProperties": true
944
+ },
945
+ "template": {
946
+ "type": "object",
947
+ "title": "template",
948
+ "description": "Format a string with Kernel#format-style placeholders (`%{key}` / `%<key>s`). `%{self}` is the current selector value; other keys resolve sibling selectors.",
949
+ "examples": [
950
+ {
951
+ "name": "template",
952
+ "string": "`%{self}` (`%{price}`)"
953
+ }
954
+ ],
955
+ "properties": {
956
+ "name": {
957
+ "type": "string",
958
+ "const": "template"
959
+ },
960
+ "string": {
961
+ "type": "string"
962
+ }
963
+ },
964
+ "required": [
965
+ "name",
966
+ "string"
967
+ ],
968
+ "additionalProperties": true
969
+ }
970
+ },
971
+ "extractors": {
972
+ "attribute": {
973
+ "type": "string",
974
+ "const": "attribute",
975
+ "title": "attribute",
976
+ "description": "Return the value of an HTML attribute on the selected element. Requires sibling selector option `attribute` (attribute name).",
977
+ "examples": [
978
+ "attribute"
979
+ ]
980
+ },
981
+ "href": {
982
+ "type": "string",
983
+ "const": "href",
984
+ "title": "href",
985
+ "description": "Return the absolute URL from the selected element's `href` attribute (relative hrefs are resolved against the channel URL).",
986
+ "examples": [
987
+ "href"
988
+ ]
989
+ },
990
+ "html": {
991
+ "type": "string",
992
+ "const": "html",
993
+ "title": "html",
994
+ "description": "Return the outer HTML of the selected element. Sanitize during post-processing (e.g. `sanitize_html`).",
995
+ "examples": [
996
+ "html"
997
+ ]
998
+ },
999
+ "static": {
1000
+ "type": "string",
1001
+ "const": "static",
1002
+ "title": "static",
1003
+ "description": "Return a fixed value from sibling selector option `static` (no DOM read).",
1004
+ "examples": [
1005
+ "static"
1006
+ ]
1007
+ },
1008
+ "text": {
1009
+ "type": "string",
1010
+ "const": "text",
1011
+ "title": "text",
1012
+ "description": "Return collapsed visible text of the selected element (default extractor).",
1013
+ "examples": [
1014
+ "text"
1015
+ ]
1016
+ }
1017
+ }
1018
+ }
676
1019
  }