html-to-markdown 3.4.0 → 3.6.0.pre.rc.24

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 (188) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +347 -0
  4. data/Steepfile +10 -2
  5. data/ext/html_to_markdown_rb/Cargo.toml +3 -2
  6. data/ext/html_to_markdown_rb/extconf.rb +5 -5
  7. data/ext/html_to_markdown_rb/native/Cargo.lock +962 -0
  8. data/ext/html_to_markdown_rb/native/Cargo.toml +6 -11
  9. data/ext/html_to_markdown_rb/native/extconf.rb +14 -0
  10. data/ext/html_to_markdown_rb/src/lib.rs +1715 -646
  11. data/lib/html_to_markdown/native.rb +913 -37
  12. data/lib/html_to_markdown/version.rb +3 -3
  13. data/lib/html_to_markdown.rb +9 -4
  14. data/lib/html_to_markdown_rb.so +0 -0
  15. data/sig/types.rbs +59 -292
  16. metadata +32 -179
  17. data/ext/html_to_markdown_rb/Makefile +0 -592
  18. data/lib/bin/html-to-markdown +0 -0
  19. data/vendor/Cargo.toml +0 -33
  20. data/vendor/html-to-markdown-rs/Cargo.toml +0 -54
  21. data/vendor/html-to-markdown-rs/README.md +0 -278
  22. data/vendor/html-to-markdown-rs/examples/basic.rs +0 -24
  23. data/vendor/html-to-markdown-rs/examples/table.rs +0 -25
  24. data/vendor/html-to-markdown-rs/examples/test_deser.rs +0 -12
  25. data/vendor/html-to-markdown-rs/examples/test_escape.rs +0 -58
  26. data/vendor/html-to-markdown-rs/examples/test_inline_formatting.rs +0 -113
  27. data/vendor/html-to-markdown-rs/examples/test_lists.rs +0 -39
  28. data/vendor/html-to-markdown-rs/examples/test_semantic_tags.rs +0 -89
  29. data/vendor/html-to-markdown-rs/examples/test_tables.rs +0 -100
  30. data/vendor/html-to-markdown-rs/examples/test_task_lists.rs +0 -61
  31. data/vendor/html-to-markdown-rs/examples/test_whitespace.rs +0 -34
  32. data/vendor/html-to-markdown-rs/src/convert_api.rs +0 -349
  33. data/vendor/html-to-markdown-rs/src/converter/block/blockquote.rs +0 -178
  34. data/vendor/html-to-markdown-rs/src/converter/block/container.rs +0 -114
  35. data/vendor/html-to-markdown-rs/src/converter/block/div.rs +0 -149
  36. data/vendor/html-to-markdown-rs/src/converter/block/heading.rs +0 -428
  37. data/vendor/html-to-markdown-rs/src/converter/block/horizontal_rule.rs +0 -103
  38. data/vendor/html-to-markdown-rs/src/converter/block/line_break.rs +0 -89
  39. data/vendor/html-to-markdown-rs/src/converter/block/mod.rs +0 -10
  40. data/vendor/html-to-markdown-rs/src/converter/block/paragraph.rs +0 -140
  41. data/vendor/html-to-markdown-rs/src/converter/block/preformatted.rs +0 -298
  42. data/vendor/html-to-markdown-rs/src/converter/block/table/builder.rs +0 -453
  43. data/vendor/html-to-markdown-rs/src/converter/block/table/caption.rs +0 -44
  44. data/vendor/html-to-markdown-rs/src/converter/block/table/cell.rs +0 -276
  45. data/vendor/html-to-markdown-rs/src/converter/block/table/cells.rs +0 -336
  46. data/vendor/html-to-markdown-rs/src/converter/block/table/layout.rs +0 -58
  47. data/vendor/html-to-markdown-rs/src/converter/block/table/mod.rs +0 -266
  48. data/vendor/html-to-markdown-rs/src/converter/block/table/scanner.rs +0 -146
  49. data/vendor/html-to-markdown-rs/src/converter/block/table/utils.rs +0 -34
  50. data/vendor/html-to-markdown-rs/src/converter/block/unknown.rs +0 -138
  51. data/vendor/html-to-markdown-rs/src/converter/context.rs +0 -208
  52. data/vendor/html-to-markdown-rs/src/converter/dom_context.rs +0 -337
  53. data/vendor/html-to-markdown-rs/src/converter/form/elements.rs +0 -770
  54. data/vendor/html-to-markdown-rs/src/converter/form/mod.rs +0 -82
  55. data/vendor/html-to-markdown-rs/src/converter/format/djot.rs +0 -64
  56. data/vendor/html-to-markdown-rs/src/converter/format/markdown.rs +0 -59
  57. data/vendor/html-to-markdown-rs/src/converter/format/mod.rs +0 -43
  58. data/vendor/html-to-markdown-rs/src/converter/handlers/blockquote.rs +0 -173
  59. data/vendor/html-to-markdown-rs/src/converter/handlers/code_block.rs +0 -434
  60. data/vendor/html-to-markdown-rs/src/converter/handlers/graphic.rs +0 -234
  61. data/vendor/html-to-markdown-rs/src/converter/handlers/image.rs +0 -282
  62. data/vendor/html-to-markdown-rs/src/converter/handlers/link.rs +0 -316
  63. data/vendor/html-to-markdown-rs/src/converter/handlers/mod.rs +0 -26
  64. data/vendor/html-to-markdown-rs/src/converter/inline/code.rs +0 -306
  65. data/vendor/html-to-markdown-rs/src/converter/inline/emphasis.rs +0 -345
  66. data/vendor/html-to-markdown-rs/src/converter/inline/link.rs +0 -428
  67. data/vendor/html-to-markdown-rs/src/converter/inline/mod.rs +0 -237
  68. data/vendor/html-to-markdown-rs/src/converter/inline/ruby.rs +0 -337
  69. data/vendor/html-to-markdown-rs/src/converter/inline/semantic/marks.rs +0 -566
  70. data/vendor/html-to-markdown-rs/src/converter/inline/semantic/mod.rs +0 -86
  71. data/vendor/html-to-markdown-rs/src/converter/inline/semantic/typography.rs +0 -558
  72. data/vendor/html-to-markdown-rs/src/converter/list/definition.rs +0 -232
  73. data/vendor/html-to-markdown-rs/src/converter/list/item.rs +0 -332
  74. data/vendor/html-to-markdown-rs/src/converter/list/mod.rs +0 -70
  75. data/vendor/html-to-markdown-rs/src/converter/list/ordered.rs +0 -201
  76. data/vendor/html-to-markdown-rs/src/converter/list/unordered.rs +0 -195
  77. data/vendor/html-to-markdown-rs/src/converter/list/utils.rs +0 -314
  78. data/vendor/html-to-markdown-rs/src/converter/main.rs +0 -710
  79. data/vendor/html-to-markdown-rs/src/converter/main_helpers.rs +0 -452
  80. data/vendor/html-to-markdown-rs/src/converter/media/embedded.rs +0 -393
  81. data/vendor/html-to-markdown-rs/src/converter/media/graphic.rs +0 -4
  82. data/vendor/html-to-markdown-rs/src/converter/media/image.rs +0 -183
  83. data/vendor/html-to-markdown-rs/src/converter/media/mod.rs +0 -87
  84. data/vendor/html-to-markdown-rs/src/converter/media/svg.rs +0 -280
  85. data/vendor/html-to-markdown-rs/src/converter/metadata.rs +0 -220
  86. data/vendor/html-to-markdown-rs/src/converter/mod.rs +0 -156
  87. data/vendor/html-to-markdown-rs/src/converter/plain_text.rs +0 -516
  88. data/vendor/html-to-markdown-rs/src/converter/preprocessing_helpers.rs +0 -201
  89. data/vendor/html-to-markdown-rs/src/converter/reference_collector.rs +0 -69
  90. data/vendor/html-to-markdown-rs/src/converter/semantic/attributes.rs +0 -269
  91. data/vendor/html-to-markdown-rs/src/converter/semantic/definition_list.rs +0 -266
  92. data/vendor/html-to-markdown-rs/src/converter/semantic/figure.rs +0 -391
  93. data/vendor/html-to-markdown-rs/src/converter/semantic/mod.rs +0 -112
  94. data/vendor/html-to-markdown-rs/src/converter/semantic/sectioning.rs +0 -85
  95. data/vendor/html-to-markdown-rs/src/converter/semantic/summary.rs +0 -324
  96. data/vendor/html-to-markdown-rs/src/converter/text/mod.rs +0 -8
  97. data/vendor/html-to-markdown-rs/src/converter/text/processing.rs +0 -56
  98. data/vendor/html-to-markdown-rs/src/converter/text_node.rs +0 -269
  99. data/vendor/html-to-markdown-rs/src/converter/utility/attributes.rs +0 -151
  100. data/vendor/html-to-markdown-rs/src/converter/utility/caching.rs +0 -74
  101. data/vendor/html-to-markdown-rs/src/converter/utility/content.rs +0 -271
  102. data/vendor/html-to-markdown-rs/src/converter/utility/mod.rs +0 -17
  103. data/vendor/html-to-markdown-rs/src/converter/utility/preprocessing.rs +0 -1002
  104. data/vendor/html-to-markdown-rs/src/converter/utility/serialization.rs +0 -126
  105. data/vendor/html-to-markdown-rs/src/converter/utility/siblings.rs +0 -97
  106. data/vendor/html-to-markdown-rs/src/converter/visitor_hooks.rs +0 -189
  107. data/vendor/html-to-markdown-rs/src/error.rs +0 -43
  108. data/vendor/html-to-markdown-rs/src/exports.rs +0 -24
  109. data/vendor/html-to-markdown-rs/src/inline_images.rs +0 -336
  110. data/vendor/html-to-markdown-rs/src/lib.rs +0 -139
  111. data/vendor/html-to-markdown-rs/src/metadata/collector.rs +0 -457
  112. data/vendor/html-to-markdown-rs/src/metadata/config.rs +0 -394
  113. data/vendor/html-to-markdown-rs/src/metadata/extraction.rs +0 -398
  114. data/vendor/html-to-markdown-rs/src/metadata/mod.rs +0 -288
  115. data/vendor/html-to-markdown-rs/src/metadata/types.rs +0 -477
  116. data/vendor/html-to-markdown-rs/src/options/conversion.rs +0 -559
  117. data/vendor/html-to-markdown-rs/src/options/inline_image.rs +0 -111
  118. data/vendor/html-to-markdown-rs/src/options/mod.rs +0 -20
  119. data/vendor/html-to-markdown-rs/src/options/preprocessing.rs +0 -201
  120. data/vendor/html-to-markdown-rs/src/options/validation.rs +0 -416
  121. data/vendor/html-to-markdown-rs/src/prelude.rs +0 -1
  122. data/vendor/html-to-markdown-rs/src/rcdom.rs +0 -487
  123. data/vendor/html-to-markdown-rs/src/text.rs +0 -358
  124. data/vendor/html-to-markdown-rs/src/types/document.rs +0 -191
  125. data/vendor/html-to-markdown-rs/src/types/mod.rs +0 -17
  126. data/vendor/html-to-markdown-rs/src/types/result.rs +0 -54
  127. data/vendor/html-to-markdown-rs/src/types/structure_builder.rs +0 -791
  128. data/vendor/html-to-markdown-rs/src/types/structure_collector.rs +0 -483
  129. data/vendor/html-to-markdown-rs/src/types/tables.rs +0 -52
  130. data/vendor/html-to-markdown-rs/src/types/warnings.rs +0 -33
  131. data/vendor/html-to-markdown-rs/src/validation.rs +0 -158
  132. data/vendor/html-to-markdown-rs/src/visitor/default_impl.rs +0 -63
  133. data/vendor/html-to-markdown-rs/src/visitor/mod.rs +0 -41
  134. data/vendor/html-to-markdown-rs/src/visitor/traits.rs +0 -370
  135. data/vendor/html-to-markdown-rs/src/visitor/types.rs +0 -319
  136. data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/callbacks/mod.rs +0 -1
  137. data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/content.rs +0 -126
  138. data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/mod.rs +0 -27
  139. data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/state.rs +0 -110
  140. data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/traversal.rs +0 -250
  141. data/vendor/html-to-markdown-rs/src/visitor_helpers.rs +0 -597
  142. data/vendor/html-to-markdown-rs/src/wrapper/sync.rs +0 -413
  143. data/vendor/html-to-markdown-rs/src/wrapper/utils.rs +0 -290
  144. data/vendor/html-to-markdown-rs/src/wrapper.rs +0 -9
  145. data/vendor/html-to-markdown-rs/tests/br_in_inline_test.rs +0 -87
  146. data/vendor/html-to-markdown-rs/tests/commonmark_compliance_test.rs +0 -297
  147. data/vendor/html-to-markdown-rs/tests/djot_output_test.rs +0 -153
  148. data/vendor/html-to-markdown-rs/tests/exclude_selectors_test.rs +0 -132
  149. data/vendor/html-to-markdown-rs/tests/integration_test.rs +0 -631
  150. data/vendor/html-to-markdown-rs/tests/issue_121_regressions.rs +0 -49
  151. data/vendor/html-to-markdown-rs/tests/issue_127_regressions.rs +0 -58
  152. data/vendor/html-to-markdown-rs/tests/issue_128_regressions.rs +0 -17
  153. data/vendor/html-to-markdown-rs/tests/issue_131_regressions.rs +0 -41
  154. data/vendor/html-to-markdown-rs/tests/issue_134_regressions.rs +0 -40
  155. data/vendor/html-to-markdown-rs/tests/issue_139_regressions.rs +0 -26
  156. data/vendor/html-to-markdown-rs/tests/issue_140_regressions.rs +0 -185
  157. data/vendor/html-to-markdown-rs/tests/issue_143_regressions.rs +0 -100
  158. data/vendor/html-to-markdown-rs/tests/issue_145_regressions.rs +0 -133
  159. data/vendor/html-to-markdown-rs/tests/issue_146_regressions.rs +0 -144
  160. data/vendor/html-to-markdown-rs/tests/issue_176_regressions.rs +0 -62
  161. data/vendor/html-to-markdown-rs/tests/issue_190_regressions.rs +0 -128
  162. data/vendor/html-to-markdown-rs/tests/issue_199_regressions.rs +0 -20
  163. data/vendor/html-to-markdown-rs/tests/issue_200_regressions.rs +0 -62
  164. data/vendor/html-to-markdown-rs/tests/issue_212_regressions.rs +0 -68
  165. data/vendor/html-to-markdown-rs/tests/issue_216_217_regressions.rs +0 -87
  166. data/vendor/html-to-markdown-rs/tests/issue_336_regressions.rs +0 -74
  167. data/vendor/html-to-markdown-rs/tests/issue_339_regressions.rs +0 -92
  168. data/vendor/html-to-markdown-rs/tests/issue_347_regressions.rs +0 -154
  169. data/vendor/html-to-markdown-rs/tests/issue_348_visitor_plain.rs +0 -93
  170. data/vendor/html-to-markdown-rs/tests/json_ld_script_extraction.rs +0 -44
  171. data/vendor/html-to-markdown-rs/tests/lists_test.rs +0 -199
  172. data/vendor/html-to-markdown-rs/tests/plain_output_test.rs +0 -273
  173. data/vendor/html-to-markdown-rs/tests/preprocessing_tests.rs +0 -61
  174. data/vendor/html-to-markdown-rs/tests/reference_links_test.rs +0 -169
  175. data/vendor/html-to-markdown-rs/tests/sectioning_elements_test.rs +0 -137
  176. data/vendor/html-to-markdown-rs/tests/skip_images_test.rs +0 -522
  177. data/vendor/html-to-markdown-rs/tests/tables_test.rs +0 -743
  178. data/vendor/html-to-markdown-rs/tests/test_custom_elements.rs +0 -41
  179. data/vendor/html-to-markdown-rs/tests/test_issue_187.rs +0 -204
  180. data/vendor/html-to-markdown-rs/tests/test_issue_218.rs +0 -68
  181. data/vendor/html-to-markdown-rs/tests/test_issue_277.rs +0 -77
  182. data/vendor/html-to-markdown-rs/tests/test_max_depth.rs +0 -82
  183. data/vendor/html-to-markdown-rs/tests/test_nested_simple.rs +0 -45
  184. data/vendor/html-to-markdown-rs/tests/test_script_style_stripping.rs +0 -396
  185. data/vendor/html-to-markdown-rs/tests/test_spa_bisect.rs +0 -34
  186. data/vendor/html-to-markdown-rs/tests/visitor_code_integration_test.rs +0 -121
  187. data/vendor/html-to-markdown-rs/tests/visitor_integration_test.rs +0 -1190
  188. data/vendor/html-to-markdown-rs/tests/xml_tables_test.rs +0 -372
@@ -1,201 +0,0 @@
1
- //! HTML preprocessing and validation helpers.
2
- //!
3
- //! This module contains helper functions for preprocessing HTML before conversion,
4
- //! including validation and normalization checks.
5
-
6
- use crate::converter::dom_context::DomContext;
7
- use crate::converter::main_helpers::is_inline_element;
8
- use crate::converter::utility::attributes::{attribute_matches_any, element_has_navigation_hint};
9
- use crate::converter::utility::content::normalized_tag_name;
10
- use crate::options::ConversionOptions;
11
-
12
- /// Check if an inline ancestor element is allowed to contain block-level elements.
13
- pub fn inline_ancestor_allows_block(tag_name: &str) -> bool {
14
- matches!(tag_name, "a" | "ins" | "del")
15
- }
16
-
17
- /// Detect block elements that were incorrectly nested under inline ancestors.
18
- ///
19
- /// Excludes elements inside `<pre>` or `<code>` blocks, as they have special
20
- /// whitespace preservation rules and should not be repaired.
21
- ///
22
- /// Also detects table structural elements (`td`, `tr`, `th`) nested under `<p>` —
23
- /// a structural impossibility in valid HTML that signals the `tl` parser absorbed
24
- /// a table into a paragraph because of an unclosed `<p>` (common in Word/Outlook
25
- /// HTML such as `<p class='MsoNormal'>` cells). Issue #336.
26
- pub fn has_inline_block_misnest(dom_ctx: &DomContext, parser: &tl::Parser) -> bool {
27
- for handle in dom_ctx.node_map.iter().flatten() {
28
- if let Some(tl::Node::Tag(_tag)) = handle.get(parser) {
29
- let node_id = handle.get_inner();
30
- let Some(info) = dom_ctx.tag_info(node_id, parser) else {
31
- continue;
32
- };
33
-
34
- // Table elements under <p>: tl misparsed an unclosed <p> in <td>.
35
- if matches!(info.name.as_str(), "td" | "tr" | "th") && has_p_ancestor(dom_ctx, parser, node_id) {
36
- return true;
37
- }
38
-
39
- if !info.is_block {
40
- continue;
41
- }
42
-
43
- // Check if this block element or any ancestor is pre/code
44
- let mut check_parent = Some(node_id);
45
- let mut inside_preformatted = false;
46
- while let Some(check_id) = check_parent {
47
- if let Some(info) = dom_ctx.tag_info(check_id, parser) {
48
- if matches!(info.name.as_str(), "pre" | "code") {
49
- inside_preformatted = true;
50
- break;
51
- }
52
- }
53
- check_parent = dom_ctx.parent_of(check_id);
54
- }
55
-
56
- // Skip misnesting check for elements inside pre/code blocks
57
- if inside_preformatted {
58
- continue;
59
- }
60
-
61
- let mut current = dom_ctx.parent_of(node_id);
62
- while let Some(parent_id) = current {
63
- if let Some(parent_info) = dom_ctx.tag_info(parent_id, parser) {
64
- if is_inline_element(&parent_info.name) && !inline_ancestor_allows_block(&parent_info.name) {
65
- return true;
66
- }
67
- } else if let Some(parent_handle) = dom_ctx.node_handle(parent_id) {
68
- if let Some(tl::Node::Tag(parent_tag)) = parent_handle.get(parser) {
69
- let parent_name = normalized_tag_name(parent_tag.name().as_utf8_str());
70
- if is_inline_element(&parent_name) && !inline_ancestor_allows_block(&parent_name) {
71
- return true;
72
- }
73
- }
74
- }
75
- current = dom_ctx.parent_of(parent_id);
76
- }
77
- }
78
- }
79
-
80
- false
81
- }
82
-
83
- /// Walk ancestors of `node_id` looking for a `<p>` element.
84
- ///
85
- /// Stops ascending once it leaves the table hierarchy (`table`/`body`/`html`)
86
- /// to avoid false positives where a `<p>` legitimately wraps a `<table>`.
87
- fn has_p_ancestor(dom_ctx: &DomContext, parser: &tl::Parser, node_id: u32) -> bool {
88
- let mut current = dom_ctx.parent_of(node_id);
89
- while let Some(parent_id) = current {
90
- if let Some(parent_info) = dom_ctx.tag_info(parent_id, parser) {
91
- if parent_info.name == "p" {
92
- return true;
93
- }
94
- if matches!(parent_info.name.as_str(), "table" | "body" | "html") {
95
- return false;
96
- }
97
- }
98
- current = dom_ctx.parent_of(parent_id);
99
- }
100
- false
101
- }
102
-
103
- /// Determine if a node should be dropped during preprocessing.
104
- ///
105
- /// Behavior depends on the [`PreprocessingPreset`]:
106
- ///
107
- /// - **Minimal**: Only scripts/styles are stripped (handled elsewhere). This function
108
- /// drops nothing — all structural elements are preserved.
109
- /// - **Standard** (default): Drops `<nav>` unconditionally. Drops `<header>`, `<footer>`,
110
- /// and `<aside>` only when they have navigation hints (class/role/aria attributes
111
- /// indicating site chrome). Drops `<form>` when `remove_forms` is enabled.
112
- /// - **Aggressive**: All of Standard, plus: drops `<footer>`, `<aside>`, `<noscript>`
113
- /// unconditionally. Drops ANY element with navigation hints in class/id/role
114
- /// (e.g. `<div class="sidebar">`). Drops elements with noise-related classes/roles.
115
- pub fn should_drop_for_preprocessing(tag_name: &str, tag: &tl::HTMLTag, options: &ConversionOptions) -> bool {
116
- use crate::options::PreprocessingPreset;
117
-
118
- if !options.preprocessing.enabled {
119
- return false;
120
- }
121
-
122
- let preset = options.preprocessing.preset;
123
-
124
- // Minimal preset: drop nothing here (scripts/styles handled in earlier pipeline stage).
125
- if preset == PreprocessingPreset::Minimal {
126
- return false;
127
- }
128
-
129
- // Form removal — applies to both Standard and Aggressive when enabled.
130
- if options.preprocessing.remove_forms && tag_name == "form" {
131
- return true;
132
- }
133
-
134
- let is_aggressive = preset == PreprocessingPreset::Aggressive;
135
-
136
- // Aggressive: drop <noscript> — its content is fallback for no-JS browsers.
137
- if is_aggressive && tag_name == "noscript" {
138
- return true;
139
- }
140
-
141
- // Navigation removal — only when the flag is enabled.
142
- if !options.preprocessing.remove_navigation {
143
- return false;
144
- }
145
-
146
- let has_nav_hint = element_has_navigation_hint(tag);
147
-
148
- // <nav> is always navigation — drop in both Standard and Aggressive.
149
- if tag_name == "nav" {
150
- return true;
151
- }
152
-
153
- if tag_name == "header" {
154
- // Drop <header> only with navigation hints (e.g. class="site-header",
155
- // role="navigation"). A plain <header> often wraps article titles like
156
- // <header><h1>Title</h1></header> — dropping it loses content.
157
- return has_nav_hint;
158
- }
159
-
160
- if tag_name == "footer" || tag_name == "aside" {
161
- // Standard: drop only with navigation hints.
162
- // Aggressive: drop unconditionally.
163
- return is_aggressive || has_nav_hint;
164
- }
165
-
166
- // Aggressive: drop ANY element that has navigation hints in class/id/role.
167
- // This catches <div class="sidebar">, <div class="menu">, <section class="navigation">,
168
- // and similar non-semantic navigation containers.
169
- if is_aggressive && has_nav_hint {
170
- return true;
171
- }
172
-
173
- // Aggressive: drop elements with noise-related roles.
174
- if is_aggressive {
175
- if element_has_noise_hint(tag) {
176
- return true;
177
- }
178
- }
179
-
180
- false
181
- }
182
-
183
- /// Check if an element has noise-related hints (ads, cookie banners, social sharing).
184
- fn element_has_noise_hint(tag: &tl::HTMLTag) -> bool {
185
- const NOISE_KEYWORDS: &[&str] = &[
186
- "cookie",
187
- "consent",
188
- "gdpr",
189
- "banner",
190
- "advertisement",
191
- "ad-container",
192
- "advert",
193
- "social-share",
194
- "share-buttons",
195
- "popup",
196
- "modal-overlay",
197
- "newsletter-signup",
198
- ];
199
-
200
- attribute_matches_any(tag, "class", NOISE_KEYWORDS) || attribute_matches_any(tag, "id", NOISE_KEYWORDS)
201
- }
@@ -1,69 +0,0 @@
1
- //! Collector for reference-style link definitions.
2
-
3
- use std::cell::RefCell;
4
- use std::collections::HashMap;
5
- use std::rc::Rc;
6
-
7
- /// Shared handle for passing the collector through the conversion context.
8
- pub type ReferenceCollectorHandle = Rc<RefCell<ReferenceCollector>>;
9
-
10
- #[derive(Debug, Clone, Hash, Eq, PartialEq)]
11
- struct ReferenceKey {
12
- url: String,
13
- title: Option<String>,
14
- }
15
-
16
- /// Collects link/image references during conversion and produces a reference
17
- /// definitions section at the end of the document.
18
- #[derive(Debug, Default)]
19
- pub struct ReferenceCollector {
20
- map: HashMap<ReferenceKey, usize>,
21
- entries: Vec<(usize, String, Option<String>)>,
22
- }
23
-
24
- impl ReferenceCollector {
25
- /// Create a new, empty reference collector.
26
- pub fn new() -> Self {
27
- Self::default()
28
- }
29
-
30
- /// Register a URL (and optional title) and return its 1-based reference number.
31
- ///
32
- /// If the same URL+title pair was already registered, the existing number is returned.
33
- pub fn get_or_insert(&mut self, url: &str, title: Option<&str>) -> usize {
34
- let key = ReferenceKey {
35
- url: url.to_string(),
36
- title: title.map(String::from),
37
- };
38
- if let Some(&num) = self.map.get(&key) {
39
- return num;
40
- }
41
- let num = self.entries.len() + 1;
42
- self.map.insert(key, num);
43
- self.entries.push((num, url.to_string(), title.map(String::from)));
44
- num
45
- }
46
-
47
- /// Produce the reference definitions section.
48
- ///
49
- /// Returns an empty string when no references were collected.
50
- pub fn finish(&self) -> String {
51
- if self.entries.is_empty() {
52
- return String::new();
53
- }
54
- let mut out = String::new();
55
- for (num, url, title) in &self.entries {
56
- out.push('[');
57
- out.push_str(&num.to_string());
58
- out.push_str("]: ");
59
- out.push_str(url);
60
- if let Some(t) = title {
61
- out.push_str(" \"");
62
- out.push_str(&t.replace('"', "\\\""));
63
- out.push('"');
64
- }
65
- out.push('\n');
66
- }
67
- out
68
- }
69
- }
@@ -1,269 +0,0 @@
1
- //! Handlers for semantic inline elements with attributes.
2
- //!
3
- //! Processes semantic inline elements that often carry semantic meaning through attributes:
4
- //! - `<cite>` - Citation/reference to a source
5
- //! - `<q>` - Inline quotation
6
- //! - `<abbr>` - Abbreviation with optional title explanation
7
- //! - `<dfn>` - Definition of a term
8
- //! - `<time>` - Machine-readable date/time
9
- //! - `<data>` - Machine-readable data value
10
- //!
11
- //! These elements carry semantic meaning in HTML5 but often have minimal
12
- //! visual distinction in rendered Markdown. Some are formatted with emphasis
13
- //! or have their attributes included in the output.
14
-
15
- // Note: Context and DomContext are defined in converter.rs
16
- // walk_node is also defined there and must be called via the parent module
17
-
18
- use crate::converter::utility::content::chomp_inline;
19
-
20
- /// Handles the `<dfn>` element.
21
- ///
22
- /// A dfn element marks a term that is being defined. The content represents
23
- /// the term, and its definition would typically appear in surrounding context.
24
- /// It is rendered as emphasized (italic) text.
25
- ///
26
- /// # Behavior
27
- ///
28
- /// - Content is collected from children
29
- /// - Non-empty content is wrapped with the configured emphasis symbol (default: `*`)
30
- /// - Inline suffix handling is applied (e.g., footnote references)
31
- pub fn handle_dfn(
32
- _tag_name: &str,
33
- node_handle: &tl::NodeHandle,
34
- parser: &tl::Parser,
35
- output: &mut String,
36
- options: &crate::options::ConversionOptions,
37
- ctx: &super::Context,
38
- depth: usize,
39
- dom_ctx: &super::DomContext,
40
- ) {
41
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
42
- let mut content = String::with_capacity(32);
43
- let children = tag.children();
44
- {
45
- for child_handle in children.top().iter() {
46
- super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
47
- }
48
- }
49
-
50
- let (prefix, suffix, trimmed) = chomp_inline(&content);
51
- if !trimmed.is_empty() {
52
- output.push_str(prefix);
53
- output.push(options.strong_em_symbol);
54
- output.push_str(trimmed);
55
- output.push(options.strong_em_symbol);
56
- append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
57
- }
58
- }
59
- }
60
-
61
- /// Handles the `<abbr>` element.
62
- ///
63
- /// An abbr element marks an abbreviation or acronym. The `title` attribute
64
- /// provides the expansion of the abbreviation, which is appended in parentheses
65
- /// if present.
66
- ///
67
- /// # Behavior
68
- ///
69
- /// - Content is collected from children
70
- /// - Non-empty content is output as-is
71
- /// - If `title` attribute exists, it is appended in parentheses: `abbr (title)`
72
- ///
73
- /// # Example
74
- ///
75
- /// ```html
76
- /// <abbr title="HyperText Markup Language">HTML</abbr>
77
- /// ```
78
- ///
79
- /// Produces: `HTML (HyperText Markup Language)`
80
- pub fn handle_abbr(
81
- _tag_name: &str,
82
- node_handle: &tl::NodeHandle,
83
- parser: &tl::Parser,
84
- output: &mut String,
85
- options: &crate::options::ConversionOptions,
86
- ctx: &super::Context,
87
- depth: usize,
88
- dom_ctx: &super::DomContext,
89
- ) {
90
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
91
- let mut content = String::with_capacity(32);
92
- let children = tag.children();
93
- {
94
- for child_handle in children.top().iter() {
95
- super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
96
- }
97
- }
98
-
99
- let trimmed = content.trim();
100
- if !trimmed.is_empty() {
101
- output.push_str(trimmed);
102
-
103
- // Append title attribute if present
104
- if let Some(title) = tag.attributes().get("title").flatten().map(|v| v.as_utf8_str()) {
105
- let trimmed_title = title.trim();
106
- if !trimmed_title.is_empty() {
107
- output.push_str(" (");
108
- output.push_str(trimmed_title);
109
- output.push(')');
110
- }
111
- }
112
- }
113
- }
114
- }
115
-
116
- /// Handles the `<time>` and `<data>` elements.
117
- ///
118
- /// Time and data elements contain machine-readable content in their attributes
119
- /// and human-readable content in their text. For Markdown purposes, we output
120
- /// only the human-readable text content, as Markdown doesn't have a way to
121
- /// preserve machine-readable metadata.
122
- ///
123
- /// # Behavior
124
- ///
125
- /// - Content is extracted from children and output as-is
126
- /// - Attributes (datetime, value) are not rendered in Markdown output
127
- pub fn handle_time_data(
128
- _tag_name: &str,
129
- node_handle: &tl::NodeHandle,
130
- parser: &tl::Parser,
131
- output: &mut String,
132
- options: &crate::options::ConversionOptions,
133
- ctx: &super::Context,
134
- depth: usize,
135
- dom_ctx: &super::DomContext,
136
- ) {
137
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
138
- let children = tag.children();
139
- {
140
- for child_handle in children.top().iter() {
141
- super::walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
142
- }
143
- }
144
- }
145
- }
146
-
147
- /// Handles the `<cite>` element.
148
- ///
149
- /// A cite element marks the title of a cited work (book, article, website, etc.).
150
- /// It is rendered as emphasized (italic) text in block mode, or as plain text in inline mode.
151
- ///
152
- /// # Behavior
153
- ///
154
- /// - **Block mode**: Content is wrapped with emphasis markers (default: `*`)
155
- /// - **Inline mode**: Content is output as-is without formatting
156
- pub fn handle_cite(
157
- _tag_name: &str,
158
- node_handle: &tl::NodeHandle,
159
- parser: &tl::Parser,
160
- output: &mut String,
161
- options: &crate::options::ConversionOptions,
162
- ctx: &super::Context,
163
- depth: usize,
164
- dom_ctx: &super::DomContext,
165
- ) {
166
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
167
- let mut content = String::with_capacity(32);
168
- let children = tag.children();
169
- {
170
- for child_handle in children.top().iter() {
171
- super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
172
- }
173
- }
174
-
175
- let trimmed = content.trim();
176
- if !trimmed.is_empty() {
177
- if ctx.convert_as_inline {
178
- output.push_str(trimmed);
179
- } else {
180
- output.push('*');
181
- output.push_str(trimmed);
182
- output.push('*');
183
- }
184
- }
185
- }
186
- }
187
-
188
- /// Handles the `<q>` element.
189
- ///
190
- /// A q element marks an inline quotation. In Markdown, it is rendered as
191
- /// quoted text enclosed in double quotes. Backslashes and quotes within
192
- /// the content are escaped.
193
- ///
194
- /// # Behavior
195
- ///
196
- /// - **Block mode**: Content is wrapped in escaped double quotes: `"content"`
197
- /// - **Inline mode**: Content is output as-is without quotes
198
- ///
199
- /// # Escaping
200
- ///
201
- /// Internal backslashes and double quotes are escaped:
202
- /// - `\` → `\\`
203
- /// - `"` → `\"`
204
- pub fn handle_q(
205
- _tag_name: &str,
206
- node_handle: &tl::NodeHandle,
207
- parser: &tl::Parser,
208
- output: &mut String,
209
- options: &crate::options::ConversionOptions,
210
- ctx: &super::Context,
211
- depth: usize,
212
- dom_ctx: &super::DomContext,
213
- ) {
214
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
215
- let mut content = String::with_capacity(32);
216
- let children = tag.children();
217
- {
218
- for child_handle in children.top().iter() {
219
- super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
220
- }
221
- }
222
-
223
- let trimmed = content.trim();
224
- if !trimmed.is_empty() {
225
- output.push('"');
226
- output.push_str(trimmed);
227
- output.push('"');
228
- }
229
- }
230
- }
231
-
232
- /// Dispatcher for semantic inline attribute elements.
233
- ///
234
- /// Routes `<cite>`, `<q>`, `<abbr>`, `<dfn>`, `<time>`, and `<data>` elements
235
- /// to their respective handlers.
236
- pub fn handle(
237
- tag_name: &str,
238
- node_handle: &tl::NodeHandle,
239
- parser: &tl::Parser,
240
- output: &mut String,
241
- options: &crate::options::ConversionOptions,
242
- ctx: &super::Context,
243
- depth: usize,
244
- dom_ctx: &super::DomContext,
245
- ) {
246
- match tag_name {
247
- "dfn" => handle_dfn(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
248
- "abbr" => handle_abbr(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
249
- "time" | "data" => handle_time_data(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
250
- "cite" => handle_cite(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
251
- "q" => handle_q(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
252
- _ => {}
253
- }
254
- }
255
-
256
- /// Appends inline suffix to the output.
257
- ///
258
- /// This is a placeholder for integrating with other inline formatting systems
259
- /// (e.g., footnote references). For now, it simply outputs the suffix.
260
- fn append_inline_suffix(
261
- output: &mut String,
262
- suffix: &str,
263
- _is_nonempty: bool,
264
- _node_handle: &tl::NodeHandle,
265
- _parser: &tl::Parser,
266
- _dom_ctx: &super::DomContext,
267
- ) {
268
- output.push_str(suffix);
269
- }