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,144 +0,0 @@
1
- #![allow(missing_docs)]
2
-
3
- use html_to_markdown_rs::ConversionOptions;
4
-
5
- #[test]
6
- fn test_strip_tags_prevents_metadata_extraction() {
7
- let html = r#"<!DOCTYPE html>
8
- <html>
9
- <head>
10
- <title>Test Document</title>
11
- <meta name="author" content="John Doe">
12
- <meta name="description" content="A test document">
13
- <meta property="og:title" content="Test OG Title">
14
- </head>
15
- <body>
16
- <p>Main content here</p>
17
- </body>
18
- </html>"#;
19
-
20
- let options = ConversionOptions {
21
- extract_metadata: true,
22
- strip_tags: vec!["meta".to_string()],
23
- ..Default::default()
24
- };
25
-
26
- let result = convert(html, Some(options)).unwrap();
27
-
28
- assert!(
29
- result.contains("Main content here"),
30
- "Body content should be preserved: {result}"
31
- );
32
-
33
- assert!(
34
- result.contains("title: Test Document"),
35
- "Title should still be extracted in frontmatter: {result}"
36
- );
37
-
38
- assert!(
39
- !result.contains("meta-author"),
40
- "meta-author should NOT be in frontmatter when strip_tags=['meta']: {result}"
41
- );
42
- assert!(
43
- !result.contains("meta-description"),
44
- "meta-description should NOT be in frontmatter when strip_tags=['meta']: {result}"
45
- );
46
- assert!(
47
- !result.contains("meta-og-title"),
48
- "meta-og-title should NOT be in frontmatter when strip_tags=['meta']: {result}"
49
- );
50
- }
51
-
52
- #[test]
53
- fn test_strip_tags_title_prevents_extraction() {
54
- let html = r#"<!DOCTYPE html>
55
- <html>
56
- <head>
57
- <title>Should Be Stripped</title>
58
- <meta name="author" content="Jane Smith">
59
- <meta name="keywords" content="test, demo">
60
- </head>
61
- <body>
62
- <h1>Document Heading</h1>
63
- <p>Some content</p>
64
- </body>
65
- </html>"#;
66
-
67
- let options = ConversionOptions {
68
- extract_metadata: true,
69
- strip_tags: vec!["title".to_string()],
70
- ..Default::default()
71
- };
72
-
73
- let result = convert(html, Some(options)).unwrap();
74
-
75
- assert!(
76
- result.contains("Document Heading") && result.contains("Some content"),
77
- "Body content should be preserved: {result}"
78
- );
79
-
80
- assert!(
81
- result.contains("meta-author"),
82
- "meta-author should still be extracted when only title is stripped: {result}"
83
- );
84
- assert!(
85
- result.contains("meta-keywords"),
86
- "meta-keywords should still be extracted when only title is stripped: {result}"
87
- );
88
-
89
- assert!(
90
- !result.contains("title: Should Be Stripped"),
91
- "title should NOT be in frontmatter when strip_tags=['title']: {result}"
92
- );
93
- }
94
-
95
- #[test]
96
- fn test_preserve_tags_prevents_metadata_extraction() {
97
- let html = r#"<!DOCTYPE html>
98
- <html>
99
- <head>
100
- <title>Preserved Title</title>
101
- <meta name="viewport" content="width=device-width">
102
- <meta name="author" content="Test Author">
103
- </head>
104
- <body>
105
- <div>
106
- <p>Body content</p>
107
- </div>
108
- </body>
109
- </html>"#;
110
-
111
- let options = ConversionOptions {
112
- extract_metadata: true,
113
- preserve_tags: vec!["meta".to_string()],
114
- ..Default::default()
115
- };
116
-
117
- let result = convert(html, Some(options)).unwrap();
118
-
119
- assert!(
120
- result.contains("Body content"),
121
- "Body content should be preserved: {result}"
122
- );
123
-
124
- assert!(
125
- result.contains("title: Preserved Title"),
126
- "title should still be extracted in frontmatter: {result}"
127
- );
128
-
129
- assert!(
130
- !result.contains("meta-viewport"),
131
- "meta-viewport should NOT be in YAML frontmatter when preserve_tags=['meta']: {result}"
132
- );
133
- assert!(
134
- !result.contains("meta-author"),
135
- "meta-author should NOT be in YAML frontmatter when preserve_tags=['meta']: {result}"
136
- );
137
- }
138
-
139
- fn convert(
140
- html: &str,
141
- opts: Option<html_to_markdown_rs::ConversionOptions>,
142
- ) -> html_to_markdown_rs::error::Result<String> {
143
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
144
- }
@@ -1,62 +0,0 @@
1
- #![allow(missing_docs)]
2
-
3
- //! Regression tests for issue #176: Newlines not preserved with adjacent blockquotes
4
-
5
- #[test]
6
- fn test_strong_blockquote_strong_newlines() {
7
- fn convert(
8
- html: &str,
9
- opts: Option<html_to_markdown_rs::ConversionOptions>,
10
- ) -> html_to_markdown_rs::error::Result<String> {
11
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
12
- }
13
-
14
- // Test case from issue #176: strong + blockquote + strong
15
- let html = r"<strong>2. Point two</strong><blockquote>Option Explicit
16
- Sub Test()
17
- ' code here
18
- End Function</blockquote><strong>3. Point three</strong>";
19
-
20
- let markdown = convert(html, None).unwrap();
21
-
22
- println!("Actual output:\n{markdown}");
23
- println!("---");
24
-
25
- // Should have blank lines separating elements
26
- assert!(
27
- markdown.contains("**2. Point two**\n\n>"),
28
- "Should have blank line between strong and blockquote. Got: {markdown:?}"
29
- );
30
- assert!(
31
- markdown.contains("End Function\n\n**3. Point three**"),
32
- "Should have blank line between blockquote and next strong. Got: {markdown:?}"
33
- );
34
- }
35
-
36
- #[test]
37
- fn test_paragraph_blockquote_paragraph_newlines() {
38
- fn convert(
39
- html: &str,
40
- opts: Option<html_to_markdown_rs::ConversionOptions>,
41
- ) -> html_to_markdown_rs::error::Result<String> {
42
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
43
- }
44
-
45
- // Control test: p + blockquote + p should work correctly
46
- let html = r"<p>First paragraph</p><blockquote>A quote</blockquote><p>Second paragraph</p>";
47
-
48
- let markdown = convert(html, None).unwrap();
49
-
50
- println!("Actual output:\n{markdown}");
51
-
52
- // Should have single newline before blockquote (CommonMark spec)
53
- // and blank line after blockquote
54
- assert!(
55
- markdown.contains("First paragraph\n>"),
56
- "Should have single newline between p and blockquote (CommonMark compliance)"
57
- );
58
- assert!(
59
- markdown.contains("A quote\n\n"),
60
- "Should have blank line after blockquote"
61
- );
62
- }
@@ -1,128 +0,0 @@
1
- //! Regression coverage for issue #190.
2
-
3
- fn convert(
4
- html: &str,
5
- opts: Option<html_to_markdown_rs::ConversionOptions>,
6
- ) -> html_to_markdown_rs::error::Result<String> {
7
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
8
- }
9
-
10
- use std::fs;
11
- use std::path::PathBuf;
12
-
13
- use html_to_markdown_rs::{CodeBlockStyle, ConversionOptions};
14
-
15
- fn fixture_path(name: &str) -> PathBuf {
16
- [
17
- env!("CARGO_MANIFEST_DIR"),
18
- "../../test_documents/html/issues/gh-190",
19
- name,
20
- ]
21
- .iter()
22
- .collect()
23
- }
24
-
25
- fn read_fixture_lossy(name: &str) -> String {
26
- let bytes = fs::read(fixture_path(name)).expect("read issue #190 fixture");
27
- String::from_utf8_lossy(&bytes).into_owned()
28
- }
29
-
30
- fn decode_utf16_without_bom(bytes: &[u8]) -> String {
31
- let mut even_nul = 0usize;
32
- let mut odd_nul = 0usize;
33
- for (idx, &byte) in bytes.iter().enumerate() {
34
- if byte == 0 {
35
- if idx % 2 == 0 {
36
- even_nul += 1;
37
- } else {
38
- odd_nul += 1;
39
- }
40
- }
41
- }
42
-
43
- let is_little_endian = odd_nul >= even_nul;
44
- let mut units = Vec::with_capacity(bytes.len() / 2);
45
- let mut chunks = bytes.chunks_exact(2);
46
- for chunk in &mut chunks {
47
- let unit = if is_little_endian {
48
- u16::from_le_bytes([chunk[0], chunk[1]])
49
- } else {
50
- u16::from_be_bytes([chunk[0], chunk[1]])
51
- };
52
- units.push(unit);
53
- }
54
-
55
- String::from_utf16_lossy(&units)
56
- }
57
-
58
- #[test]
59
- fn test_code_block_dedent_handles_unicode_whitespace() {
60
- let nbsp = '\u{00A0}';
61
- let html = format!("<pre><code> msg = String()\n{nbsp}msg = String()\n</code></pre>");
62
- let options = ConversionOptions {
63
- code_block_style: CodeBlockStyle::Backticks,
64
- ..Default::default()
65
- };
66
-
67
- let markdown = convert(&html, Some(options)).expect("conversion should succeed");
68
-
69
- assert!(markdown.contains("msg = String()"));
70
- assert!(!markdown.contains(nbsp));
71
- }
72
-
73
- #[test]
74
- fn test_convert_strips_nul_bytes() {
75
- let html = "a\0b";
76
- let markdown = convert(html, None).expect("conversion should succeed");
77
-
78
- assert_eq!(markdown, "ab\n");
79
- }
80
-
81
- #[test]
82
- fn converts_all_issue_190_fixtures_except_known_utf16_binary() {
83
- let fixtures = [
84
- "firsteigen.html",
85
- "vipaarontours.html",
86
- "ozonekorea.html",
87
- "mitrade.html",
88
- "plusblog.html",
89
- "maxkim.html",
90
- "insight.html",
91
- "flex2025.html",
92
- "flex2021.html",
93
- "kimbrain.html",
94
- "rbloggers.html",
95
- "sjsu.html",
96
- ];
97
-
98
- for name in fixtures {
99
- let html = read_fixture_lossy(name);
100
- let markdown =
101
- convert(&html, None).unwrap_or_else(|err| panic!("fixture {name} should convert cleanly: {err}"));
102
- assert!(!markdown.trim().is_empty(), "fixture {name} produced empty markdown");
103
- }
104
- }
105
-
106
- #[test]
107
- fn converts_sjsu_fixture_when_lossy_utf8_is_auto_decoded() {
108
- let bytes = fs::read(fixture_path("sjsu.html")).expect("read sjsu fixture bytes");
109
- let raw_html = String::from_utf8_lossy(&bytes).into_owned();
110
-
111
- let markdown = convert(&raw_html, None).expect("lossy UTF-16 HTML should be recovered and converted");
112
- assert!(
113
- markdown.contains("pipeline") || markdown.contains("Pipeline"),
114
- "auto-decoded sjsu fixture should contain expected content"
115
- );
116
- }
117
-
118
- #[test]
119
- fn converts_sjsu_fixture_when_decoded_as_utf16() {
120
- let bytes = fs::read(fixture_path("sjsu.html")).expect("read sjsu fixture bytes");
121
- let decoded_html = decode_utf16_without_bom(&bytes);
122
-
123
- let markdown = convert(&decoded_html, None).expect("decoded UTF-16 HTML should convert");
124
- assert!(
125
- markdown.contains("pipeline") || markdown.contains("Pipeline"),
126
- "decoded sjsu fixture should contain expected content"
127
- );
128
- }
@@ -1,20 +0,0 @@
1
- //! Regression coverage for issue #199.
2
-
3
- fn convert(
4
- html: &str,
5
- opts: Option<html_to_markdown_rs::ConversionOptions>,
6
- ) -> html_to_markdown_rs::error::Result<String> {
7
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
8
- }
9
-
10
- #[test]
11
- fn test_link_label_is_not_truncated() {
12
- let label = "a".repeat(600);
13
- let html = format!(r#"<a href="https://example.com">{label}</a>"#);
14
-
15
- let markdown = convert(&html, None).expect("conversion should succeed");
16
- let expected = format!("[{label}](https://example.com)");
17
-
18
- assert!(markdown.contains(&expected));
19
- assert!(!markdown.contains('…'));
20
- }
@@ -1,62 +0,0 @@
1
- //! Regression coverage for issues #200 and #214.
2
-
3
- fn convert(
4
- html: &str,
5
- opts: Option<html_to_markdown_rs::ConversionOptions>,
6
- ) -> html_to_markdown_rs::error::Result<String> {
7
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
8
- }
9
-
10
- #[test]
11
- fn test_definition_list_spacing_consistency() {
12
- let html1 = r#"
13
- <div>
14
- <dl>
15
- <dt>Tags:</dt>
16
- <dd>
17
- <ul>
18
- <li>
19
- <a href="https://site.com">php</a>
20
- </li>
21
- <li>
22
- <a href="https://site.com/search/">closure</a>
23
- </li>
24
- </ul>
25
- <button type="button">Add tags</button>
26
- </dd>
27
- </dl>
28
- </div>
29
- "#;
30
-
31
- let html2 = r#"<div><dl><dt>Tags:</dt><dd><ul><li><a href="https://site.com">php</a></li><li><a href="https://site.com/search/">closure</a></li></ul><button type="button">Add tags</button></dd></dl></div>"#;
32
-
33
- let markdown1 = convert(html1, None).expect("conversion should succeed");
34
- let markdown2 = convert(html2, None).expect("conversion should succeed");
35
-
36
- assert_eq!(markdown1, markdown2);
37
- assert!(markdown1.contains("Tags:"));
38
- assert!(markdown1.contains("[php]"));
39
- assert!(markdown1.contains("[closure]"));
40
- assert!(markdown1.contains("Add tags"));
41
- // Ensure no Pandoc definition list colon prefix is introduced
42
- assert!(!markdown1.contains(": "));
43
- }
44
-
45
- /// Regression test for issue #214: colon introduced into text from dd elements.
46
- #[test]
47
- fn test_definition_list_no_colon_prefix() {
48
- let html = r#"<dl>
49
- <dt id="canRequestFocus" class="property inherited">
50
- <span class="name"><a href="material/InkResponse/canRequestFocus.html">canRequestFocus</a></span>
51
- <span class="signature">&#8594; <a href="dart-core/bool-class.html">bool</a></span>
52
- </dt>
53
- <dd class="inherited">
54
- If true, this widget may request the primary focus.
55
- <div class="features"><span class="feature">final</span><span class="feature">inherited</span></div>
56
- </dd>
57
- </dl>"#;
58
- let markdown = convert(html, None).expect("conversion should succeed");
59
- assert!(markdown.contains("If true, this widget may request the primary focus."));
60
- // The dd content must not be prefixed with ": " (Pandoc definition list syntax)
61
- assert!(!markdown.contains(": If true"));
62
- }
@@ -1,68 +0,0 @@
1
- #![allow(missing_docs)]
2
-
3
- fn convert(
4
- html: &str,
5
- opts: Option<html_to_markdown_rs::ConversionOptions>,
6
- ) -> html_to_markdown_rs::error::Result<String> {
7
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
8
- }
9
-
10
- /// Regression test for <https://github.com/kreuzberg-dev/html-to-markdown/issues/212>
11
- ///
12
- /// When `\n` precedes an `<a>` tag inside a `<p>`, the whitespace was
13
- /// inconsistently handled between the first and subsequent paragraphs.
14
- /// The bug was stateful: identical HTML structures produced different
15
- /// results depending on their position in the document.
16
- #[test]
17
- fn consistent_whitespace_before_link_across_paragraphs_issue_212() {
18
- let html = r#"<p>text before
19
- <a href="https://example.com">the link</a>
20
- after</p>
21
- <p>text before
22
- <a href="https://example.com">the link</a>
23
- after</p>"#;
24
-
25
- let result = convert(html, None).unwrap();
26
- assert_eq!(
27
- result,
28
- "text before [the link](https://example.com) after\n\ntext before [the link](https://example.com) after\n"
29
- );
30
- }
31
-
32
- /// Same bug but with three paragraphs to ensure no accumulation effects.
33
- #[test]
34
- fn consistent_whitespace_three_paragraphs_issue_212() {
35
- let html = r#"<p>click
36
- <a href="/a">here</a></p>
37
- <p>click
38
- <a href="/b">here</a></p>
39
- <p>click
40
- <a href="/c">here</a></p>"#;
41
-
42
- let result = convert(html, None).unwrap();
43
- assert_eq!(result, "click [here](/a)\n\nclick [here](/b)\n\nclick [here](/c)\n");
44
- }
45
-
46
- /// Verify the fix doesn't break whitespace between text nodes without links.
47
- #[test]
48
- fn newline_before_inline_elements_consistent_issue_212() {
49
- let html = r"<p>before
50
- <strong>bold</strong> after</p>
51
- <p>before
52
- <strong>bold</strong> after</p>";
53
-
54
- let result = convert(html, None).unwrap();
55
- assert_eq!(result, "before **bold** after\n\nbefore **bold** after\n");
56
- }
57
-
58
- /// Verify with `<em>` tags across multiple paragraphs.
59
- #[test]
60
- fn newline_before_em_across_paragraphs_issue_212() {
61
- let html = r"<p>some text
62
- <em>emphasized</em> end</p>
63
- <p>some text
64
- <em>emphasized</em> end</p>";
65
-
66
- let result = convert(html, None).unwrap();
67
- assert_eq!(result, "some text *emphasized* end\n\nsome text *emphasized* end\n");
68
- }
@@ -1,87 +0,0 @@
1
- //! Regression tests for issues #216 and #217.
2
- //!
3
- //! Both issues report a panic at `text_node.rs:191`:
4
- //! "byte index N is out of bounds of \`\`"
5
- //!
6
- //! Root cause: When inline handlers (strong, em, etc.) collect children into a
7
- //! fresh String buffer while inheriting a parent context with `block_content_start`
8
- //! set by a paragraph handler, the index points into the wrong buffer.
9
-
10
- fn convert(
11
- html: &str,
12
- opts: Option<html_to_markdown_rs::ConversionOptions>,
13
- ) -> html_to_markdown_rs::error::Result<String> {
14
- html_to_markdown_rs::convert(html, opts).map(|r| r.content.unwrap_or_default())
15
- }
16
-
17
- /// Minimal reproducer: a <details> containing a <p> with <strong> inside.
18
- /// The <details> handler collects into a fresh buffer, the <p> sets
19
- /// `block_content_start`, and the <strong> handler creates yet another fresh
20
- /// buffer — causing the index to be out of bounds.
21
- #[test]
22
- fn test_issue_216_217_details_paragraph_strong_no_panic() {
23
- let html = r"
24
- <div>some preceding content</div>
25
- <details>
26
- <summary>Summary text</summary>
27
- <p><strong>Bold text inside details paragraph
28
- </strong></p>
29
- </details>
30
- ";
31
-
32
- let result = convert(html, None);
33
- assert!(result.is_ok());
34
- }
35
-
36
- /// Same issue can occur with emphasis inside a paragraph inside details.
37
- #[test]
38
- fn test_issue_216_217_details_paragraph_em_no_panic() {
39
- let html = r"
40
- <div>some preceding content</div>
41
- <details>
42
- <summary>Summary</summary>
43
- <p><em>Italic text inside details paragraph
44
- </em></p>
45
- </details>
46
- ";
47
-
48
- let result = convert(html, None);
49
- assert!(result.is_ok());
50
- }
51
-
52
- /// The panic can also occur with nested inline elements inside paragraphs
53
- /// collected by any handler that creates a fresh buffer.
54
- #[test]
55
- fn test_issue_216_217_nested_strong_in_paragraph_no_panic() {
56
- let html = r"
57
- <details>
58
- <p>Some text <strong>bold text with trailing newline
59
- </strong> more text</p>
60
- </details>
61
- ";
62
-
63
- let result = convert(html, None);
64
- assert!(result.is_ok());
65
- }
66
-
67
- /// Test with the actual structure pattern from the reported URL.
68
- #[test]
69
- fn test_issue_216_217_complex_details_structure() {
70
- let html = r#"
71
- <div class="content">
72
- <section>
73
- <details>
74
- <summary>Stratégie adaptation</summary>
75
- <p><strong>Risque élevé pour les populations
76
- </strong></p>
77
- <p>Description du risque avec des détails supplémentaires.</p>
78
- </details>
79
- </section>
80
- </div>
81
- "#;
82
-
83
- let result = convert(html, None);
84
- assert!(result.is_ok());
85
- let md = result.unwrap();
86
- assert!(md.contains("Risque"));
87
- }
@@ -1,74 +0,0 @@
1
- #![allow(missing_docs)]
2
-
3
- //! Regression tests for issue #336: content after a table is silently truncated when an
4
- //! unclosed `<p>` appears inside a `<td>` without a closing `</p>`.
5
- //!
6
- //! The `tl` parser mishandles malformed Word/Outlook HTML (produced by Microsoft Word's
7
- //! HTML export) where a `<p>` tag inside a `<td>` is not properly closed before the
8
- //! `</td>` end tag. When the unclosed `<p>` appears in a table cell, `tl` treats all
9
- //! subsequent sibling elements — including other `<td>` cells and all post-table content —
10
- //! as descendants of that `<p>`.
11
- //!
12
- //! The most visible symptom: any `<p>`, `<h2>`, or other element that comes **after** the
13
- //! closing `</table>` tag gets swallowed into the last table cell and is never emitted as
14
- //! top-level document content, causing the output to appear truncated.
15
- //!
16
- //! Triggered by the `<td><p class='MsoNormal'>&nbsp;</td>` pattern that appears 34 times
17
- //! in `ms-fscc.html` — specifically when that pattern is the *first* `<td>` in a row.
18
-
19
- fn convert(html: &str) -> String {
20
- html_to_markdown_rs::convert(html, None)
21
- .expect("conversion should not fail")
22
- .content
23
- .unwrap_or_default()
24
- }
25
-
26
- /// Minimal reproduction: a table with two rows each having an unclosed `<p>` as the first
27
- /// `<td>`. The `<p>SectionAfterTable</p>` that follows MUST appear in the output.
28
- ///
29
- /// Without the fix, `tl` nests the post-table `<p>` inside the last table `<td>`, so the
30
- /// converter either drops it (blank-table fast-return) or renders it inline inside a table
31
- /// cell — in both cases it is absent as a top-level paragraph.
32
- #[test]
33
- fn test_content_after_table_not_dropped_when_td_has_unclosed_p() {
34
- let html = r"<table>
35
- <tr>
36
- <td width=1><p class='MsoNormal'>&nbsp;</td>
37
- <td>Row1Data</td>
38
- </tr>
39
- <tr>
40
- <td width=1><p class='MsoNormal'>&nbsp;</td>
41
- <td>Row2Data</td>
42
- </tr>
43
- </table>
44
- <p>SectionAfterTable</p>";
45
-
46
- let result = convert(html);
47
- assert!(
48
- result.contains("SectionAfterTable"),
49
- "Content after the table must not be swallowed into the table DOM. Got:\n{result}"
50
- );
51
- }
52
-
53
- /// Headings and subsequent sections after such a table must also be preserved.
54
- #[test]
55
- fn test_headings_after_table_with_msonormal_p_in_td_are_preserved() {
56
- let html = r"<table>
57
- <tr>
58
- <td width=1><p class='MsoNormal'>&nbsp;</td>
59
- <td>Some data</td>
60
- </tr>
61
- </table>
62
- <h2>Section 2.4.22</h2>
63
- <p>This section describes something important.</p>";
64
-
65
- let result = convert(html);
66
- assert!(
67
- result.contains("Section 2.4.22"),
68
- "Heading after the table must appear in the output. Got:\n{result}"
69
- );
70
- assert!(
71
- result.contains("This section describes something important"),
72
- "Paragraph after the heading must appear. Got:\n{result}"
73
- );
74
- }