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,566 +0,0 @@
1
- //! Handlers for mark/highlight and strikethrough/underline elements.
2
- //!
3
- //! Contains:
4
- //! - Mark (highlight) element with configurable styles
5
- //! - Strikethrough (del, s tags) with ~~ syntax
6
- //! - Inserted/underlined text (ins, u tags) with == syntax
7
-
8
- #[cfg(feature = "visitor")]
9
- use crate::converter::utility::content::collect_tag_attributes;
10
- use crate::options::{ConversionOptions, OutputFormat};
11
- #[cfg(feature = "visitor")]
12
- use std::collections::BTreeMap;
13
- use tl::{NodeHandle, Parser};
14
-
15
- type Context = crate::converter::Context;
16
- type DomContext = crate::converter::DomContext;
17
-
18
- /// Handle mark (highlight) element with configurable styles.
19
- ///
20
- /// Supports multiple highlight styles:
21
- /// - DoubleEqual: `==highlighted==`
22
- /// - Html: `<mark>highlighted</mark>`
23
- /// - Bold: `**highlighted**`
24
- /// - None: just pass through content
25
- pub fn handle_mark(
26
- node_handle: &NodeHandle,
27
- parser: &Parser,
28
- output: &mut String,
29
- options: &ConversionOptions,
30
- ctx: &Context,
31
- depth: usize,
32
- dom_ctx: &DomContext,
33
- ) {
34
- #[allow(unused_imports)]
35
- use crate::converter::{get_text_content, serialize_node, walk_node};
36
-
37
- let Some(node) = node_handle.get(parser) else { return };
38
-
39
- let tag = match node {
40
- tl::Node::Tag(tag) => tag,
41
- _ => return,
42
- };
43
-
44
- #[cfg(feature = "visitor")]
45
- let mark_output = if let Some(ref visitor_handle) = ctx.visitor {
46
- use crate::visitor::{NodeContext, NodeType, VisitResult};
47
-
48
- let text_content = get_text_content(node_handle, parser, dom_ctx);
49
- let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
50
-
51
- let node_id = node_handle.get_inner();
52
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
53
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
54
-
55
- let node_ctx = NodeContext {
56
- node_type: NodeType::Mark,
57
- tag_name: tag.name().as_utf8_str().to_string(),
58
- attributes,
59
- depth,
60
- index_in_parent,
61
- parent_tag,
62
- is_inline: true,
63
- };
64
-
65
- let visit_result = {
66
- let mut visitor = visitor_handle.borrow_mut();
67
- visitor.visit_mark(&node_ctx, &text_content)
68
- };
69
- match visit_result {
70
- VisitResult::Continue => None,
71
- VisitResult::Custom(custom) => Some(custom),
72
- VisitResult::Skip => Some(String::new()),
73
- VisitResult::PreserveHtml => Some(serialize_node(node_handle, parser)),
74
- VisitResult::Error(err) => {
75
- if ctx.visitor_error.borrow().is_none() {
76
- *ctx.visitor_error.borrow_mut() = Some(err);
77
- }
78
- None
79
- }
80
- }
81
- } else {
82
- None
83
- };
84
-
85
- #[cfg(feature = "visitor")]
86
- if let Some(custom_output) = mark_output {
87
- output.push_str(&custom_output);
88
- return;
89
- }
90
-
91
- if ctx.convert_as_inline {
92
- // In inline conversion context, just pass through children
93
- let children = tag.children();
94
- for child_handle in children.top().iter() {
95
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
96
- }
97
- } else {
98
- use crate::options::HighlightStyle;
99
- match options.highlight_style {
100
- HighlightStyle::DoubleEqual => {
101
- if options.output_format == OutputFormat::Djot {
102
- output.push_str("{=");
103
- } else {
104
- output.push_str("==");
105
- }
106
- let children = tag.children();
107
- for child_handle in children.top().iter() {
108
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
109
- }
110
- if options.output_format == OutputFormat::Djot {
111
- output.push_str("=}");
112
- } else {
113
- output.push_str("==");
114
- }
115
- }
116
- HighlightStyle::Html => {
117
- output.push_str("<mark>");
118
- let children = tag.children();
119
- for child_handle in children.top().iter() {
120
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
121
- }
122
- output.push_str("</mark>");
123
- }
124
- HighlightStyle::Bold => {
125
- let mut symbol = String::with_capacity(2);
126
- symbol.push(options.strong_em_symbol);
127
- symbol.push(options.strong_em_symbol);
128
- output.push_str(&symbol);
129
- let bold_ctx = Context {
130
- in_strong: true,
131
- ..ctx.clone()
132
- };
133
- let children = tag.children();
134
- for child_handle in children.top().iter() {
135
- walk_node(child_handle, parser, output, options, &bold_ctx, depth + 1, dom_ctx);
136
- }
137
- output.push_str(&symbol);
138
- }
139
- HighlightStyle::None => {
140
- let children = tag.children();
141
- for child_handle in children.top().iter() {
142
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
143
- }
144
- }
145
- }
146
- }
147
- }
148
-
149
- /// Handle strikethrough element (del, s tags).
150
- ///
151
- /// Converts to `~~content~~` syntax. Suppresses formatting in code context.
152
- /// Supports visitor callbacks when the visitor feature is enabled.
153
- #[allow(unused_variables)]
154
- pub fn handle_strikethrough(
155
- tag_name: &str,
156
- node_handle: &NodeHandle,
157
- parser: &Parser,
158
- output: &mut String,
159
- options: &ConversionOptions,
160
- ctx: &Context,
161
- depth: usize,
162
- dom_ctx: &DomContext,
163
- ) {
164
- use crate::converter::{append_inline_suffix, chomp_inline, walk_node};
165
-
166
- let Some(node) = node_handle.get(parser) else { return };
167
-
168
- let tag = match node {
169
- tl::Node::Tag(tag) => tag,
170
- _ => return,
171
- };
172
-
173
- if ctx.in_code {
174
- // Suppress strikethrough in code context, just process children
175
- let children = tag.children();
176
- for child_handle in children.top().iter() {
177
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
178
- }
179
- } else {
180
- let mut content = String::with_capacity(32);
181
- let children = tag.children();
182
- for child_handle in children.top().iter() {
183
- walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
184
- }
185
-
186
- #[cfg(feature = "visitor")]
187
- let strikethrough_output = if let Some(ref visitor_handle) = ctx.visitor {
188
- use crate::converter::get_text_content;
189
- use crate::visitor::{NodeContext, NodeType, VisitResult};
190
- let text_content = get_text_content(node_handle, parser, dom_ctx);
191
- let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
192
-
193
- let node_id = node_handle.get_inner();
194
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
195
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
196
-
197
- let node_ctx = NodeContext {
198
- node_type: NodeType::Strikethrough,
199
- tag_name: tag_name.to_string(),
200
- attributes,
201
- depth,
202
- index_in_parent,
203
- parent_tag,
204
- is_inline: true,
205
- };
206
-
207
- let visit_result = {
208
- let mut visitor = visitor_handle.borrow_mut();
209
- visitor.visit_strikethrough(&node_ctx, &text_content)
210
- };
211
- match visit_result {
212
- VisitResult::Continue => None,
213
- VisitResult::Custom(custom) => Some(custom),
214
- VisitResult::Skip => Some(String::new()),
215
- VisitResult::PreserveHtml => {
216
- use crate::converter::serialize_node;
217
- Some(serialize_node(node_handle, parser))
218
- }
219
- VisitResult::Error(err) => {
220
- if ctx.visitor_error.borrow().is_none() {
221
- *ctx.visitor_error.borrow_mut() = Some(err);
222
- }
223
- None
224
- }
225
- }
226
- } else {
227
- None
228
- };
229
-
230
- #[cfg(feature = "visitor")]
231
- if let Some(custom_output) = strikethrough_output {
232
- output.push_str(&custom_output);
233
- } else {
234
- let (prefix, suffix, trimmed) = chomp_inline(&content);
235
- if !content.trim().is_empty() {
236
- output.push_str(prefix);
237
- if options.output_format == OutputFormat::Djot {
238
- output.push_str("{-");
239
- } else {
240
- output.push_str("~~");
241
- }
242
- output.push_str(trimmed);
243
- if options.output_format == OutputFormat::Djot {
244
- output.push_str("-}");
245
- } else {
246
- output.push_str("~~");
247
- }
248
- append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
249
- } else if !content.is_empty() {
250
- output.push_str(prefix);
251
- append_inline_suffix(output, suffix, false, node_handle, parser, dom_ctx);
252
- }
253
- }
254
-
255
- #[cfg(not(feature = "visitor"))]
256
- {
257
- let (prefix, suffix, trimmed) = chomp_inline(&content);
258
- if !content.trim().is_empty() {
259
- output.push_str(prefix);
260
- if options.output_format == OutputFormat::Djot {
261
- output.push_str("{-");
262
- } else {
263
- output.push_str("~~");
264
- }
265
- output.push_str(trimmed);
266
- if options.output_format == OutputFormat::Djot {
267
- output.push_str("-}");
268
- } else {
269
- output.push_str("~~");
270
- }
271
- append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
272
- } else if !content.is_empty() {
273
- output.push_str(prefix);
274
- append_inline_suffix(output, suffix, false, node_handle, parser, dom_ctx);
275
- }
276
- }
277
- }
278
- }
279
-
280
- /// Handle inserted/underlined text (ins tag).
281
- ///
282
- /// Converts to `==content==` syntax. Supports visitor callbacks when enabled.
283
- pub fn handle_inserted(
284
- node_handle: &NodeHandle,
285
- parser: &Parser,
286
- output: &mut String,
287
- options: &ConversionOptions,
288
- ctx: &Context,
289
- depth: usize,
290
- dom_ctx: &DomContext,
291
- ) {
292
- use crate::converter::{append_inline_suffix, chomp_inline, walk_node};
293
-
294
- let Some(node) = node_handle.get(parser) else { return };
295
-
296
- let tag = match node {
297
- tl::Node::Tag(tag) => tag,
298
- _ => return,
299
- };
300
-
301
- let mut content = String::with_capacity(32);
302
- let children = tag.children();
303
- for child_handle in children.top().iter() {
304
- walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
305
- }
306
-
307
- #[cfg(feature = "visitor")]
308
- let underline_output = if let Some(ref visitor_handle) = ctx.visitor {
309
- use crate::converter::get_text_content;
310
- use crate::visitor::{NodeContext, NodeType, VisitResult};
311
-
312
- let text_content = get_text_content(node_handle, parser, dom_ctx);
313
- let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
314
-
315
- let node_id = node_handle.get_inner();
316
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
317
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
318
-
319
- let node_ctx = NodeContext {
320
- node_type: NodeType::Underline,
321
- tag_name: "ins".to_string(),
322
- attributes,
323
- depth,
324
- index_in_parent,
325
- parent_tag,
326
- is_inline: true,
327
- };
328
-
329
- let visit_result = {
330
- let mut visitor = visitor_handle.borrow_mut();
331
- visitor.visit_underline(&node_ctx, &text_content)
332
- };
333
- match visit_result {
334
- VisitResult::Continue => None,
335
- VisitResult::Custom(custom) => Some(custom),
336
- VisitResult::Skip => Some(String::new()),
337
- VisitResult::PreserveHtml => {
338
- use crate::converter::serialize_node;
339
- Some(serialize_node(node_handle, parser))
340
- }
341
- VisitResult::Error(err) => {
342
- if ctx.visitor_error.borrow().is_none() {
343
- *ctx.visitor_error.borrow_mut() = Some(err);
344
- }
345
- None
346
- }
347
- }
348
- } else {
349
- None
350
- };
351
-
352
- #[cfg(feature = "visitor")]
353
- if let Some(custom_output) = underline_output {
354
- output.push_str(&custom_output);
355
- } else {
356
- let (prefix, suffix, trimmed) = chomp_inline(&content);
357
- if !trimmed.is_empty() {
358
- output.push_str(prefix);
359
- if options.output_format == OutputFormat::Djot {
360
- output.push_str("{+");
361
- } else {
362
- output.push_str("==");
363
- }
364
- output.push_str(trimmed);
365
- if options.output_format == OutputFormat::Djot {
366
- output.push_str("+}");
367
- } else {
368
- output.push_str("==");
369
- }
370
- append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
371
- }
372
- }
373
-
374
- #[cfg(not(feature = "visitor"))]
375
- {
376
- let (prefix, suffix, trimmed) = chomp_inline(&content);
377
- if !trimmed.is_empty() {
378
- output.push_str(prefix);
379
- if options.output_format == OutputFormat::Djot {
380
- output.push_str("{+");
381
- } else {
382
- output.push_str("==");
383
- }
384
- output.push_str(trimmed);
385
- if options.output_format == OutputFormat::Djot {
386
- output.push_str("+}");
387
- } else {
388
- output.push_str("==");
389
- }
390
- append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
391
- }
392
- }
393
- }
394
-
395
- /// Handle underline element (u tag).
396
- ///
397
- /// Just passes through content (HTML doesn't have native underline in Markdown).
398
- /// Supports visitor callbacks when enabled, which can provide custom formatting.
399
- pub fn handle_underline(
400
- node_handle: &NodeHandle,
401
- parser: &Parser,
402
- output: &mut String,
403
- options: &ConversionOptions,
404
- ctx: &Context,
405
- depth: usize,
406
- dom_ctx: &DomContext,
407
- ) {
408
- use crate::converter::walk_node;
409
-
410
- let Some(node) = node_handle.get(parser) else { return };
411
-
412
- let tag = match node {
413
- tl::Node::Tag(tag) => tag,
414
- _ => return,
415
- };
416
-
417
- #[cfg(feature = "visitor")]
418
- if let Some(ref visitor_handle) = ctx.visitor {
419
- use crate::converter::get_text_content;
420
- use crate::visitor::{NodeContext, NodeType, VisitResult};
421
-
422
- let text_content = get_text_content(node_handle, parser, dom_ctx);
423
- let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
424
-
425
- let node_id = node_handle.get_inner();
426
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
427
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
428
-
429
- let node_ctx = NodeContext {
430
- node_type: NodeType::Underline,
431
- tag_name: "u".to_string(),
432
- attributes,
433
- depth,
434
- index_in_parent,
435
- parent_tag,
436
- is_inline: true,
437
- };
438
-
439
- let visit_result = {
440
- let mut visitor = visitor_handle.borrow_mut();
441
- visitor.visit_underline(&node_ctx, &text_content)
442
- };
443
- match visit_result {
444
- VisitResult::Continue => {
445
- let children = tag.children();
446
- for child_handle in children.top().iter() {
447
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
448
- }
449
- }
450
- VisitResult::Custom(custom) => {
451
- output.push_str(&custom);
452
- }
453
- VisitResult::Skip => {}
454
- VisitResult::PreserveHtml => {
455
- use crate::converter::serialize_node;
456
- output.push_str(&serialize_node(node_handle, parser));
457
- }
458
- VisitResult::Error(err) => {
459
- if ctx.visitor_error.borrow().is_none() {
460
- *ctx.visitor_error.borrow_mut() = Some(err);
461
- }
462
- let children = tag.children();
463
- for child_handle in children.top().iter() {
464
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
465
- }
466
- }
467
- }
468
- } else {
469
- let children = tag.children();
470
- for child_handle in children.top().iter() {
471
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
472
- }
473
- }
474
-
475
- #[cfg(not(feature = "visitor"))]
476
- {
477
- let children = tag.children();
478
- for child_handle in children.top().iter() {
479
- walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
480
- }
481
- }
482
- }
483
-
484
- #[cfg(all(test, feature = "visitor"))]
485
- mod tests {
486
- use crate::convert;
487
- use crate::options::ConversionOptions;
488
- use crate::visitor::{HtmlVisitor, NodeContext, VisitResult};
489
- use std::cell::RefCell;
490
- use std::rc::Rc;
491
-
492
- #[derive(Debug)]
493
- struct MarkSkipVisitor;
494
-
495
- impl HtmlVisitor for MarkSkipVisitor {
496
- fn visit_mark(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
497
- VisitResult::Skip
498
- }
499
- }
500
-
501
- #[derive(Debug)]
502
- struct MarkCustomVisitor;
503
-
504
- impl HtmlVisitor for MarkCustomVisitor {
505
- fn visit_mark(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
506
- VisitResult::Custom("REPLACED".to_string())
507
- }
508
- }
509
-
510
- #[derive(Debug)]
511
- struct MarkPreserveVisitor;
512
-
513
- impl HtmlVisitor for MarkPreserveVisitor {
514
- fn visit_mark(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
515
- VisitResult::PreserveHtml
516
- }
517
- }
518
-
519
- fn make_visitor<V: HtmlVisitor + 'static>(v: V) -> ConversionOptions {
520
- ConversionOptions {
521
- visitor: Some(Rc::new(RefCell::new(v))),
522
- ..ConversionOptions::default()
523
- }
524
- }
525
-
526
- #[test]
527
- fn test_visitor_mark_skip() {
528
- let html = "<p>before <mark>highlighted</mark> after</p>";
529
- let result = convert(html, Some(make_visitor(MarkSkipVisitor))).unwrap();
530
- let content = result.content.unwrap_or_default();
531
- assert!(
532
- !content.contains("highlighted"),
533
- "mark content should be absent: {}",
534
- content
535
- );
536
- assert!(
537
- content.contains("before"),
538
- "surrounding text should be present: {}",
539
- content
540
- );
541
- }
542
-
543
- #[test]
544
- fn test_visitor_mark_custom() {
545
- let html = "<p>before <mark>highlighted</mark> after</p>";
546
- let result = convert(html, Some(make_visitor(MarkCustomVisitor))).unwrap();
547
- let content = result.content.unwrap_or_default();
548
- assert!(
549
- content.contains("REPLACED"),
550
- "custom output should be present: {}",
551
- content
552
- );
553
- }
554
-
555
- #[test]
556
- fn test_visitor_mark_preserve_html() {
557
- let html = "<p>before <mark>highlighted</mark> after</p>";
558
- let result = convert(html, Some(make_visitor(MarkPreserveVisitor))).unwrap();
559
- let content = result.content.unwrap_or_default();
560
- assert!(
561
- content.contains("<mark>highlighted</mark>"),
562
- "original html should be preserved: {}",
563
- content
564
- );
565
- }
566
- }
@@ -1,86 +0,0 @@
1
- //! Handler for semantic inline elements (mark, del, s, ins, u, small, sub, sup, var, dfn, abbr, span).
2
- //!
3
- //! Converts HTML semantic tags to Markdown formatting with support for:
4
- //! - Highlight/mark element with configurable styles (==, ::, ^^, <mark>)
5
- //! - Strikethrough (del, s tags) with ~~ syntax
6
- //! - Underline/inserted text (ins, u tags) with == syntax
7
- //! - Small text (passes through without formatting)
8
- //! - Subscript and superscript with configurable symbols
9
- //! - Variable (var) and definition (dfn) text with italic formatting
10
- //! - Abbreviation (abbr) text with optional title attribute
11
- //! - Span element with special handling for OCR words and whitespace
12
- //! - Visitor callbacks for custom processing (feature-gated)
13
-
14
- mod marks;
15
- mod typography;
16
-
17
- use crate::options::ConversionOptions;
18
- use tl::{NodeHandle, Parser};
19
-
20
- type Context = crate::converter::Context;
21
- type DomContext = crate::converter::DomContext;
22
-
23
- /// Handler for semantic inline elements: mark, del, s, ins, u, small, sub, sup, var, dfn, abbr, span.
24
- ///
25
- /// Processes semantic content based on tag and options:
26
- /// - Mark: configurable highlight style (==, ::, ^^, <mark>, **bold, none)
27
- /// - Del/S: strikethrough with ~~ and visitor callback support
28
- /// - Ins: underline with == and visitor callback support
29
- /// - U: underline with visitor callback support
30
- /// - Small: pass through without formatting
31
- /// - Sub/Sup: wrap with configurable symbols
32
- /// - Var: wrap with italic symbol (strong_em_symbol)
33
- /// - Dfn: wrap with italic symbol (strong_em_symbol)
34
- /// - Abbr: pass through content with optional title in parentheses
35
- /// - Span: pass through content with special handling for OCR words and whitespace
36
- ///
37
- /// # Note
38
- /// This function references helper functions and `walk_node` from converter.rs
39
- /// which must be accessible (pub(crate)) for this module to work correctly.
40
- pub fn handle(
41
- tag_name: &str,
42
- node_handle: &NodeHandle,
43
- parser: &Parser,
44
- output: &mut String,
45
- options: &ConversionOptions,
46
- ctx: &Context,
47
- depth: usize,
48
- dom_ctx: &DomContext,
49
- ) {
50
- match tag_name {
51
- "mark" => {
52
- marks::handle_mark(node_handle, parser, output, options, ctx, depth, dom_ctx);
53
- }
54
- "del" | "s" => {
55
- marks::handle_strikethrough(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
56
- }
57
- "ins" => {
58
- marks::handle_inserted(node_handle, parser, output, options, ctx, depth, dom_ctx);
59
- }
60
- "u" => {
61
- marks::handle_underline(node_handle, parser, output, options, ctx, depth, dom_ctx);
62
- }
63
- "small" => {
64
- typography::handle_small(node_handle, parser, output, options, ctx, depth, dom_ctx);
65
- }
66
- "sub" => {
67
- typography::handle_subscript(node_handle, parser, output, options, ctx, depth, dom_ctx);
68
- }
69
- "sup" => {
70
- typography::handle_superscript(node_handle, parser, output, options, ctx, depth, dom_ctx);
71
- }
72
- "var" => {
73
- typography::handle_variable(node_handle, parser, output, options, ctx, depth, dom_ctx);
74
- }
75
- "dfn" => {
76
- typography::handle_definition(node_handle, parser, output, options, ctx, depth, dom_ctx);
77
- }
78
- "abbr" => {
79
- typography::handle_abbreviation(node_handle, parser, output, options, ctx, depth, dom_ctx);
80
- }
81
- "span" => {
82
- typography::handle_span(node_handle, parser, output, options, ctx, depth, dom_ctx);
83
- }
84
- _ => {}
85
- }
86
- }