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,112 +0,0 @@
1
- //! Semantic HTML5 element handlers for HTML to Markdown conversion.
2
- //!
3
- //! This module provides specialized handlers for semantic HTML5 elements:
4
- //! - Sectioning elements (article, section, nav, aside, header, footer, main)
5
- //! - Figure elements (figure, figcaption)
6
- //! - Interactive elements (details, summary, dialog)
7
- //! - Semantic inline attributes (cite, q, abbr, dfn, time, data)
8
- //!
9
- //! These handlers are designed to be extracted from the main `converter.rs`
10
- //! file and integrated through the dispatcher function.
11
- //!
12
- //! **Integration Pattern:**
13
- //! Each handler function takes the same signature:
14
- //! - `tag_name: &str` - The HTML tag being processed
15
- //! - `node_handle: &NodeHandle` - The DOM node handle
16
- //! - `parser: &Parser` - The HTML parser reference
17
- //! - `output: &mut String` - The output buffer to write to
18
- //! - `options: &ConversionOptions` - Conversion configuration
19
- //! - `ctx: &Context` - Processing context (state tracking)
20
- //! - `depth: usize` - Current DOM tree depth
21
- //! - `dom_ctx: &DomContext` - DOM context for tree relationships
22
- //!
23
- //! The main dispatcher function `dispatch_semantic_handler` routes tags to
24
- //! their appropriate handlers and returns a boolean indicating success.
25
-
26
- pub mod attributes;
27
- pub mod definition_list;
28
- pub mod figure;
29
- pub mod sectioning;
30
- pub mod summary;
31
-
32
- // Re-export types from parent module for submodule access
33
- pub use super::walk_node;
34
- pub use super::{Context, DomContext};
35
-
36
- // Re-export handler functions for direct use
37
- pub use attributes::handle as handle_attributes;
38
- pub use definition_list::handle as handle_definition_list;
39
- pub use figure::handle as handle_figure;
40
- pub use sectioning::handle as handle_sectioning;
41
- pub use summary::handle as handle_summary;
42
-
43
- // Re-exports are done via the dispatch function parameter types
44
-
45
- /// Dispatches semantic element handling to the appropriate handler.
46
- ///
47
- /// This function routes semantic HTML5 elements to their specialized handlers
48
- /// based on tag name. It is designed to be called from the main `walk_node`
49
- /// function in `converter.rs`.
50
- ///
51
- /// # Routing Table
52
- ///
53
- /// The following tag routes are supported:
54
- /// - **Sectioning**: article, section, nav, aside, header, footer, main
55
- /// - **Figure**: figure, figcaption
56
- /// - **Summary**: details, summary, dialog
57
- /// - **Definition List**: hgroup, dl, dt, dd, menu
58
- /// - **Attributes**: cite, q, abbr, dfn, time, data
59
- ///
60
- /// # Returns
61
- ///
62
- /// Returns `true` if the tag was successfully handled by a semantic handler,
63
- /// `false` if the tag is not a semantic element and requires other handling.
64
- ///
65
- /// # Example
66
- ///
67
- /// ```text
68
- /// if dispatch_semantic_handler(tag_name, &node_handle, &parser, output, options, ctx, depth, dom_ctx) {
69
- /// // Tag was handled
70
- /// } else {
71
- /// // Continue with other handlers
72
- /// }
73
- /// ```
74
- pub fn dispatch_semantic_handler(
75
- tag_name: &str,
76
- node_handle: &tl::NodeHandle,
77
- parser: &tl::Parser,
78
- output: &mut String,
79
- options: &crate::options::ConversionOptions,
80
- ctx: &super::Context,
81
- depth: usize,
82
- dom_ctx: &super::DomContext,
83
- ) -> bool {
84
- match tag_name {
85
- // Sectioning elements
86
- "article" | "section" | "nav" | "aside" | "header" | "footer" | "main" => {
87
- handle_sectioning(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
88
- true
89
- }
90
- // Figure elements
91
- "figure" | "figcaption" => {
92
- handle_figure(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
93
- true
94
- }
95
- // Summary and interactive elements
96
- "details" | "summary" | "dialog" => {
97
- handle_summary(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
98
- true
99
- }
100
- // Definition list and related elements
101
- "hgroup" | "dl" | "dt" | "dd" | "menu" => {
102
- handle_definition_list(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
103
- true
104
- }
105
- // Semantic inline attributes
106
- "cite" | "q" | "abbr" | "dfn" | "time" | "data" => {
107
- handle_attributes(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx);
108
- true
109
- }
110
- _ => false,
111
- }
112
- }
@@ -1,85 +0,0 @@
1
- //! Handlers for HTML5 sectioning elements.
2
- //!
3
- //! Processes semantic sectioning elements:
4
- //! - `<article>` - Independent, self-contained content
5
- //! - `<section>` - Generic grouping of thematic content
6
- //! - `<nav>` - Navigation links (typically rendered inline or in sidebars)
7
- //! - `<aside>` - Peripheral content (sidebars, callouts)
8
- //! - `<header>` - Introductory content (page headers)
9
- //! - `<footer>` - End content (page footers)
10
- //! - `<main>` - Primary content area
11
- //!
12
- //! All these elements are treated as block-level containers.
13
- //! Their content is extracted and formatted with proper spacing.
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
- /// Handles sectioning elements (article, section, nav, aside, header, footer, main).
19
- ///
20
- /// Sectioning elements are rendered as block-level containers. When in inline
21
- /// conversion mode, their content is rendered inline without block spacing.
22
- /// Otherwise, content is wrapped with proper blank lines to separate from other blocks.
23
- ///
24
- /// # Behavior
25
- ///
26
- /// - **Inline mode**: Children are processed inline; block spacing is skipped
27
- /// - **Block mode**: Content is collected, trimmed, and formatted with blank lines
28
- /// - **Empty content**: Empty sections are skipped entirely
29
- ///
30
- /// # Implementation Note
31
- ///
32
- /// Sectioning elements act as transparent containers—their presence doesn't
33
- /// add any special formatting beyond structural grouping.
34
- pub fn handle(
35
- _tag_name: &str,
36
- node_handle: &tl::NodeHandle,
37
- parser: &tl::Parser,
38
- output: &mut String,
39
- options: &crate::options::ConversionOptions,
40
- ctx: &super::Context,
41
- depth: usize,
42
- dom_ctx: &super::DomContext,
43
- ) {
44
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
45
- // In inline context, just process children inline
46
- if ctx.convert_as_inline {
47
- let children = tag.children();
48
- {
49
- for child_handle in children.top().iter() {
50
- super::walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
51
- }
52
- }
53
- return;
54
- }
55
-
56
- // Collect content in a separate buffer
57
- let mut content = String::with_capacity(256);
58
- let children = tag.children();
59
- {
60
- for child_handle in children.top().iter() {
61
- super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
62
- }
63
- }
64
-
65
- // Skip if content is empty
66
- if content.trim().is_empty() {
67
- return;
68
- }
69
-
70
- // Add spacing before the content
71
- if !output.is_empty() && !output.ends_with("\n\n") {
72
- output.push_str("\n\n");
73
- }
74
-
75
- // Append the content
76
- output.push_str(&content);
77
-
78
- // Ensure proper spacing after the content
79
- if content.ends_with('\n') && !content.ends_with("\n\n") {
80
- output.push('\n');
81
- } else if !content.ends_with('\n') {
82
- output.push_str("\n\n");
83
- }
84
- }
85
- }
@@ -1,324 +0,0 @@
1
- //! Handlers for HTML5 interactive elements.
2
- //!
3
- //! Processes interactive disclosure and dialog semantic elements:
4
- //! - `<details>` - Expandable/collapsible disclosure widget
5
- //! - `<summary>` - Summary or caption for a details element
6
- //! - `<dialog>` - Dialog box overlay widget
7
- //!
8
- //! These elements are treated as block-level content containers
9
- //! with special formatting for the summary element.
10
-
11
- // Note: Context and DomContext are defined in converter.rs
12
- // walk_node is also defined there and must be called via the parent module
13
- use super::walk_node;
14
-
15
- /// Handles the `<details>` element.
16
- ///
17
- /// A details element represents a disclosure widget that can be toggled
18
- /// to show/hide additional content. In Markdown, it's rendered as a block
19
- /// with all content visible.
20
- ///
21
- /// # Behavior
22
- ///
23
- /// - **Inline mode**: Children are processed inline without block spacing
24
- /// - **Block mode**: Content is collected and wrapped with proper blank-line spacing
25
- /// - **Empty content**: Skipped entirely
26
- #[cfg_attr(not(feature = "visitor"), allow(unused_variables))]
27
- pub fn handle_details(
28
- _tag_name: &str,
29
- node_handle: &tl::NodeHandle,
30
- parser: &tl::Parser,
31
- output: &mut String,
32
- options: &crate::options::ConversionOptions,
33
- ctx: &super::Context,
34
- depth: usize,
35
- dom_ctx: &super::DomContext,
36
- ) {
37
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
38
- #[cfg(feature = "visitor")]
39
- if let Some(ref visitor_handle) = ctx.visitor {
40
- use crate::converter::utility::content::collect_tag_attributes;
41
- use crate::visitor::{NodeContext, NodeType, VisitResult};
42
-
43
- let attributes = collect_tag_attributes(tag);
44
- let node_id = node_handle.get_inner();
45
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
46
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
47
- let open = tag.attributes().get("open").is_some();
48
- let node_ctx = NodeContext {
49
- node_type: NodeType::Details,
50
- tag_name: "details".to_string(),
51
- attributes,
52
- depth,
53
- index_in_parent,
54
- parent_tag,
55
- is_inline: false,
56
- };
57
- let visit_result = {
58
- let mut visitor = visitor_handle.borrow_mut();
59
- visitor.visit_details(&node_ctx, open)
60
- };
61
- match visit_result {
62
- VisitResult::Continue => {}
63
- VisitResult::Skip => return,
64
- VisitResult::Custom(custom) => {
65
- if !output.is_empty() && !output.ends_with("\n\n") {
66
- output.push_str("\n\n");
67
- }
68
- output.push_str(&custom);
69
- output.push_str("\n\n");
70
- return;
71
- }
72
- VisitResult::PreserveHtml => {
73
- use crate::converter::utility::serialization::serialize_node;
74
- output.push_str(&serialize_node(node_handle, parser));
75
- return;
76
- }
77
- VisitResult::Error(err) => {
78
- if ctx.visitor_error.borrow().is_none() {
79
- *ctx.visitor_error.borrow_mut() = Some(err);
80
- }
81
- return;
82
- }
83
- }
84
- }
85
-
86
- // In inline context, just process children inline
87
- if ctx.convert_as_inline {
88
- let children = tag.children();
89
- {
90
- for child_handle in children.top().iter() {
91
- super::walk_node(child_handle, parser, output, options, ctx, depth, dom_ctx);
92
- }
93
- }
94
- return;
95
- }
96
-
97
- // Collect content
98
- let mut content = String::with_capacity(256);
99
- let children = tag.children();
100
- {
101
- for child_handle in children.top().iter() {
102
- walk_node(child_handle, parser, &mut content, options, ctx, depth, dom_ctx);
103
- }
104
- }
105
-
106
- let trimmed = content.trim();
107
- if !trimmed.is_empty() {
108
- // Add spacing before if needed
109
- if !output.is_empty() && !output.ends_with("\n\n") {
110
- output.push_str("\n\n");
111
- }
112
-
113
- // Output content
114
- output.push_str(trimmed);
115
- output.push_str("\n\n");
116
- }
117
- }
118
- }
119
-
120
- /// Handles the `<summary>` element.
121
- ///
122
- /// A summary element contains a caption for a details element.
123
- /// It is rendered as strong (bold) text to distinguish it from regular content.
124
- ///
125
- /// # Behavior
126
- ///
127
- /// - **Inline mode**: Content is rendered inline without emphasis
128
- /// - **Block mode**: Content is wrapped in strong markers (e.g., `**text**`)
129
- /// - Uses the configured strong/emphasis symbol from ConversionOptions
130
- ///
131
- /// # Implementation Details
132
- ///
133
- /// The handler:
134
- /// 1. Creates a context with `in_strong: true` for nested formatting
135
- /// 2. Collects content from all children
136
- /// 3. Wraps non-empty content in strong markers (repeated twice per Markdown spec)
137
- #[cfg_attr(not(feature = "visitor"), allow(unused_variables))]
138
- pub fn handle_summary(
139
- _tag_name: &str,
140
- node_handle: &tl::NodeHandle,
141
- parser: &tl::Parser,
142
- output: &mut String,
143
- options: &crate::options::ConversionOptions,
144
- ctx: &super::Context,
145
- depth: usize,
146
- dom_ctx: &super::DomContext,
147
- ) {
148
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
149
- let mut content = String::with_capacity(64);
150
-
151
- // Set strong context for nested content
152
- let mut summary_ctx = ctx.clone();
153
- if !ctx.convert_as_inline {
154
- summary_ctx.in_strong = true;
155
- }
156
-
157
- let children = tag.children();
158
- {
159
- for child_handle in children.top().iter() {
160
- super::walk_node(
161
- child_handle,
162
- parser,
163
- &mut content,
164
- options,
165
- &summary_ctx,
166
- depth + 1,
167
- dom_ctx,
168
- );
169
- }
170
- }
171
-
172
- let trimmed = content.trim().to_owned();
173
- if trimmed.is_empty() {
174
- return;
175
- }
176
-
177
- #[cfg(feature = "visitor")]
178
- if let Some(ref visitor_handle) = ctx.visitor {
179
- use crate::converter::utility::content::collect_tag_attributes;
180
- use crate::visitor::{NodeContext, NodeType, VisitResult};
181
-
182
- let attributes = collect_tag_attributes(tag);
183
- let node_id = node_handle.get_inner();
184
- let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
185
- let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
186
- let node_ctx = NodeContext {
187
- node_type: NodeType::Summary,
188
- tag_name: "summary".to_string(),
189
- attributes,
190
- depth,
191
- index_in_parent,
192
- parent_tag,
193
- is_inline: false,
194
- };
195
- let visit_result = {
196
- let mut visitor = visitor_handle.borrow_mut();
197
- visitor.visit_summary(&node_ctx, &trimmed)
198
- };
199
- match visit_result {
200
- VisitResult::Continue => {}
201
- VisitResult::Skip => return,
202
- VisitResult::Custom(custom) => {
203
- if ctx.convert_as_inline {
204
- output.push_str(&custom);
205
- } else {
206
- output.push_str(&custom);
207
- if !custom.ends_with('\n') {
208
- output.push_str("\n\n");
209
- }
210
- }
211
- return;
212
- }
213
- VisitResult::PreserveHtml => {
214
- use crate::converter::utility::serialization::serialize_node;
215
- output.push_str(&serialize_node(node_handle, parser));
216
- return;
217
- }
218
- VisitResult::Error(err) => {
219
- if ctx.visitor_error.borrow().is_none() {
220
- *ctx.visitor_error.borrow_mut() = Some(err);
221
- }
222
- return;
223
- }
224
- }
225
- }
226
-
227
- if ctx.convert_as_inline {
228
- // Inline mode: output without formatting
229
- output.push_str(&trimmed);
230
- } else {
231
- // Block mode: output with strong markers
232
- let mut symbol = String::with_capacity(2);
233
- symbol.push(options.strong_em_symbol);
234
- symbol.push(options.strong_em_symbol);
235
- output.push_str(&symbol);
236
- output.push_str(&trimmed);
237
- output.push_str(&symbol);
238
- output.push_str("\n\n");
239
- }
240
- }
241
- }
242
-
243
- /// Handles the `<dialog>` element.
244
- ///
245
- /// A dialog element represents a modal dialog box. In Markdown, it's rendered
246
- /// as a block container with content visible.
247
- ///
248
- /// # Behavior
249
- ///
250
- /// - **Inline mode**: Children are processed inline without block spacing
251
- /// - **Block mode**: Content is processed and wrapped with proper blank lines
252
- /// - Trailing whitespace is removed from collected content
253
- ///
254
- /// # Implementation Details
255
- ///
256
- /// The handler:
257
- /// 1. Marks the position in output before processing children
258
- /// 2. Processes all children in the normal context
259
- /// 3. Removes trailing spaces and tabs from the output
260
- /// 4. Ensures proper blank-line spacing after the dialog
261
- pub fn handle_dialog(
262
- _tag_name: &str,
263
- node_handle: &tl::NodeHandle,
264
- parser: &tl::Parser,
265
- output: &mut String,
266
- options: &crate::options::ConversionOptions,
267
- ctx: &super::Context,
268
- depth: usize,
269
- dom_ctx: &super::DomContext,
270
- ) {
271
- if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
272
- // In inline context, just process children inline
273
- if ctx.convert_as_inline {
274
- let children = tag.children();
275
- {
276
- for child_handle in children.top().iter() {
277
- super::walk_node(child_handle, parser, output, options, ctx, depth, dom_ctx);
278
- }
279
- }
280
- return;
281
- }
282
-
283
- // Mark position before processing children
284
- let content_start = output.len();
285
-
286
- let children = tag.children();
287
- {
288
- for child_handle in children.top().iter() {
289
- super::walk_node(child_handle, parser, output, options, ctx, depth, dom_ctx);
290
- }
291
- }
292
-
293
- // Remove trailing whitespace from dialog content
294
- while output.len() > content_start && (output.ends_with(' ') || output.ends_with('\t')) {
295
- output.pop();
296
- }
297
-
298
- // Ensure proper spacing after dialog
299
- if output.len() > content_start && !output.ends_with("\n\n") {
300
- output.push_str("\n\n");
301
- }
302
- }
303
- }
304
-
305
- /// Dispatcher for interactive elements.
306
- ///
307
- /// Routes `<details>`, `<summary>`, and `<dialog>` elements to their respective handlers.
308
- pub fn handle(
309
- tag_name: &str,
310
- node_handle: &tl::NodeHandle,
311
- parser: &tl::Parser,
312
- output: &mut String,
313
- options: &crate::options::ConversionOptions,
314
- ctx: &super::Context,
315
- depth: usize,
316
- dom_ctx: &super::DomContext,
317
- ) {
318
- match tag_name {
319
- "details" => handle_details(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
320
- "summary" => handle_summary(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
321
- "dialog" => handle_dialog(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
322
- _ => {}
323
- }
324
- }
@@ -1,8 +0,0 @@
1
- //! Text processing module for HTML to Markdown conversion.
2
- //!
3
- //! This module provides utilities for normalizing, escaping, and processing text content
4
- //! extracted from HTML documents during the conversion to Markdown format.
5
-
6
- mod processing;
7
-
8
- pub use processing::dedent_code_block;
@@ -1,56 +0,0 @@
1
- //! Text processing utilities for HTML to Markdown conversion.
2
- //!
3
- //! This module provides functions for processing text content, including
4
- //! code block dedentation and special character handling.
5
-
6
- /// Remove common leading whitespace from all lines in a code block.
7
- ///
8
- /// This is useful when HTML authors indent `<pre>` content for readability,
9
- /// so we can strip the shared indentation without touching meaningful spacing.
10
- ///
11
- /// # Examples
12
- ///
13
- /// ```text
14
- /// " line1\n line2" → "line1\nline2"
15
- /// " indent1\n indent2" → "indent1\n indent2" (removes 2 chars, minimum indent)
16
- /// " \n code" → "\ncode"
17
- /// ```
18
- pub fn dedent_code_block(content: &str) -> String {
19
- let lines: Vec<&str> = content.lines().collect();
20
- if lines.is_empty() {
21
- return String::new();
22
- }
23
-
24
- let min_indent = lines
25
- .iter()
26
- .filter(|line| !line.trim().is_empty())
27
- .map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
28
- .min()
29
- .unwrap_or(0);
30
-
31
- lines.iter().fold(String::new(), |mut acc, line| {
32
- if !acc.is_empty() {
33
- acc.push('\n');
34
- }
35
- let processed = if line.trim().is_empty() {
36
- *line
37
- } else {
38
- let mut remaining = min_indent;
39
- let mut cut = 0;
40
- for (idx, ch) in line.char_indices() {
41
- if remaining == 0 {
42
- break;
43
- }
44
- if ch.is_whitespace() {
45
- remaining -= 1;
46
- cut = idx + ch.len_utf8();
47
- } else {
48
- break;
49
- }
50
- }
51
- &line[cut..]
52
- };
53
- acc.push_str(processed);
54
- acc
55
- })
56
- }