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

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,743 +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
- use html_to_markdown_rs::ConversionOptions;
11
-
12
- #[test]
13
- fn test_basic_table() {
14
- let html = r"<table>
15
- <tr><th>Header 1</th><th>Header 2</th></tr>
16
- <tr><td>Cell 1</td><td>Cell 2</td></tr>
17
- </table>";
18
-
19
- let result = convert(html, None).unwrap();
20
- assert!(result.contains("| Header 1"), "header row missing: {result}");
21
- assert!(result.contains("| Header 2"), "header row missing: {result}");
22
- assert!(result.contains("| Cell 1"), "cell row missing: {result}");
23
- assert!(result.contains("| Cell 2"), "cell row missing: {result}");
24
- // Separator must exist; dashes are padded to column width so just check presence.
25
- assert!(result.contains("| ---"), "separator row missing: {result}");
26
- }
27
-
28
- #[test]
29
- fn test_table_with_sections() {
30
- let html = r"<table>
31
- <thead>
32
- <tr><th>Name</th><th>Age</th></tr>
33
- </thead>
34
- <tbody>
35
- <tr><td>John</td><td>25</td></tr>
36
- <tr><td>Jane</td><td>30</td></tr>
37
- </tbody>
38
- <tfoot>
39
- <tr><td>Total</td><td>2</td></tr>
40
- </tfoot>
41
- </table>";
42
-
43
- let result = convert(html, None).unwrap();
44
- assert!(result.contains("| Name"), "Name column missing: {result}");
45
- assert!(result.contains("| Age"), "Age column missing: {result}");
46
- assert!(result.contains("| John"), "John row missing: {result}");
47
- assert!(result.contains("| 25"), "25 cell missing: {result}");
48
- assert!(result.contains("| Jane"), "Jane row missing: {result}");
49
- assert!(result.contains("| 30"), "30 cell missing: {result}");
50
- assert!(result.contains("| Total"), "Total row missing: {result}");
51
- assert!(result.contains("| 2"), "2 cell missing: {result}");
52
- }
53
-
54
- #[test]
55
- fn test_table_caption() {
56
- let html = r"<table><caption>Table Caption</caption><tr><td>Data</td></tr></table>";
57
- let result = convert(html, None).unwrap();
58
- assert!(result.contains("*Table Caption*"));
59
- assert!(result.contains("| Data |"));
60
- }
61
-
62
- #[test]
63
- fn test_table_rowspan() {
64
- // Test table rowspan with multi-line content in cells
65
- // With br_in_tables enabled, divs are converted to br separators
66
- let html = r#"<table>
67
- <tr><th>Header 1</th><th>Header 2</th></tr>
68
- <tr><td rowspan="2">Spanning cell</td><td>
69
- <div>First row content</div>
70
- <div>Second line</div>
71
- </td></tr>
72
- <tr><td>
73
- <div>Next row</div>
74
- <div>More content</div>
75
- </td></tr>
76
- </table>"#;
77
-
78
- let options = ConversionOptions {
79
- br_in_tables: true,
80
- ..Default::default()
81
- };
82
- let result = convert(html, Some(options)).unwrap();
83
-
84
- // Content should be present; exact br format may vary
85
- assert!(
86
- result.contains("Spanning cell")
87
- && result.contains("First row content")
88
- && result.contains("Second line")
89
- && result.contains("Next row")
90
- && result.contains("More content"),
91
- "All rowspan content should be present: {result}"
92
- );
93
- }
94
-
95
- #[test]
96
- fn test_table_colspan() {
97
- let html = r#"<table>
98
- <tr><th colspan="2">Wide Header</th></tr>
99
- <tr><td>Cell 1</td><td>Cell 2</td></tr>
100
- </table>"#;
101
-
102
- let result = convert(html, None).unwrap();
103
- assert!(result.contains("| Wide Header"), "Wide Header missing: {result}");
104
- assert!(result.contains("| Cell 1"), "Cell 1 missing: {result}");
105
- assert!(result.contains("| Cell 2"), "Cell 2 missing: {result}");
106
- }
107
-
108
- #[test]
109
- fn test_table_cell_multiline_content() {
110
- // Test table cells with multiple divs (multiline content)
111
- // With br_in_tables enabled, divs should be separated by breaks
112
- let html = r"<table>
113
- <tr><th>Header 1</th><th>Header 2</th></tr>
114
- <tr><td>Cell 3</td><td>
115
- <div>Cell 4-1</div>
116
- <div>Cell 4-2</div>
117
- </td></tr>
118
- </table>";
119
-
120
- let options = ConversionOptions {
121
- br_in_tables: true,
122
- ..Default::default()
123
- };
124
- let result = convert(html, Some(options)).unwrap();
125
-
126
- // All content should be present
127
- assert!(
128
- result.contains("Header 1")
129
- && result.contains("Header 2")
130
- && result.contains("Cell 3")
131
- && result.contains("Cell 4-1")
132
- && result.contains("Cell 4-2"),
133
- "All cell content should be present: {result}"
134
- );
135
- }
136
-
137
- #[test]
138
- fn test_table_first_row_in_tbody_without_header() {
139
- let html = r"<table>
140
- <tbody>
141
- <tr><td>Cell 1</td><td>Cell 2</td></tr>
142
- </tbody>
143
- </table>";
144
-
145
- let result = convert(html, None).unwrap();
146
- // Column width is 6 chars ("Cell 1"/"Cell 2"), so separator has 6 dashes.
147
- assert!(result.contains("| Cell 1"), "Cell 1 missing: {result}");
148
- assert!(result.contains("| Cell 2"), "Cell 2 missing: {result}");
149
- assert!(result.contains("| ---"), "separator missing: {result}");
150
- }
151
-
152
- #[test]
153
- fn test_tbody_only() {
154
- let html = "<table><tbody><tr><td>Data</td></tr></tbody></table>";
155
- let result = convert(html, None).unwrap();
156
- assert!(result.contains("| Data |"));
157
- }
158
-
159
- #[test]
160
- fn test_tfoot_basic() {
161
- let html = "<table><tfoot><tr><td>Footer</td></tr></tfoot><tbody><tr><td>Data</td></tr></tbody></table>";
162
- let result = convert(html, None).unwrap();
163
- assert!(result.contains("| Footer"), "Footer cell missing: {result}");
164
- assert!(result.contains("| Data"), "Data cell missing: {result}");
165
- }
166
-
167
- #[test]
168
- fn test_caption_with_formatting() {
169
- let html = r"<table><caption>Sales <strong>Report</strong> 2023</caption><tr><td>Data</td></tr></table>";
170
- let result = convert(html, None).unwrap();
171
- assert!(result.contains("*Sales **Report** 2023*"));
172
- }
173
-
174
- #[test]
175
- fn test_table_with_links() {
176
- let html = r#"<table>
177
- <tr><th>Name</th><th>Website</th></tr>
178
- <tr><td>Example</td><td><a href="https://example.com">Link</a></td></tr>
179
- </table>"#;
180
-
181
- let result = convert(html, None).unwrap();
182
- assert!(result.contains("| Name"), "Name column missing: {result}");
183
- assert!(result.contains("| Website"), "Website column missing: {result}");
184
- assert!(result.contains("[Link](https://example.com)"), "link missing: {result}");
185
- }
186
-
187
- #[test]
188
- fn test_table_with_code() {
189
- let html = r"<table>
190
- <tr><th>Command</th></tr>
191
- <tr><td><code>ls -la</code></td></tr>
192
- </table>";
193
-
194
- let result = convert(html, None).unwrap();
195
- assert!(result.contains("| Command"), "Command column missing: {result}");
196
- assert!(result.contains("`ls -la`"), "code cell missing: {result}");
197
- }
198
-
199
- #[test]
200
- fn test_table_empty_cells() {
201
- let html = r"<table>
202
- <tr><td>Data</td><td></td></tr>
203
- </table>";
204
-
205
- let result = convert(html, None).unwrap();
206
- assert!(result.contains("| Data | |"));
207
- }
208
-
209
- #[test]
210
- fn test_table_single_column() {
211
- let html = r"<table>
212
- <tr><th>Header</th></tr>
213
- <tr><td>Cell 1</td></tr>
214
- <tr><td>Cell 2</td></tr>
215
- </table>";
216
-
217
- let result = convert(html, None).unwrap();
218
- assert!(result.contains("| Header |"), "Header row missing: {result}");
219
- // Separator dashes are padded to column width; just verify separator exists.
220
- assert!(result.contains("| ---"), "separator missing: {result}");
221
- assert!(result.contains("| Cell 1 |"), "Cell 1 row missing: {result}");
222
- assert!(result.contains("| Cell 2 |"), "Cell 2 row missing: {result}");
223
- }
224
-
225
- #[test]
226
- fn test_blogger_table_with_image() {
227
- // Regression test for Issue #175: Image tags inside Blogger table wrappers not being processed
228
- let html = r#"
229
- <table class="tr-caption-container">
230
- <a href="https://example.com/full-image.jpg">
231
- <img border="0" height="480"
232
- src="https://blogger.googleusercontent.com/img/test/IMG_0427.JPG"
233
- width="640" alt="Test Image" />
234
- </a>
235
- </table>
236
- "#;
237
-
238
- let result = convert(html, None).unwrap();
239
-
240
- // The image should be converted to markdown (wrapped in a link)
241
- assert!(
242
- result.contains("!["),
243
- "Result should contain markdown image syntax: {result}"
244
- );
245
- assert!(
246
- result.contains("blogger.googleusercontent.com"),
247
- "Result should contain image URL: {result}"
248
- );
249
- assert!(
250
- result.contains("example.com/full-image.jpg"),
251
- "Result should contain link URL: {result}"
252
- );
253
- }
254
-
255
- #[test]
256
- fn test_table_with_image_no_rows() {
257
- // Test that images in tables without proper rows are still processed
258
- let html = r#"<table><img src="https://example.com/image.jpg" alt="test image"></table>"#;
259
- let result = convert(html, None).unwrap();
260
-
261
- assert!(
262
- result.contains("![test image](https://example.com/image.jpg)"),
263
- "Image should be converted to markdown: {result}"
264
- );
265
- }
266
-
267
- #[test]
268
- fn test_table_with_link_and_image_no_rows() {
269
- // Test that link-wrapped images in tables without proper rows are processed
270
- let html =
271
- r#"<table><a href="https://example.com"><img src="https://example.com/image.jpg" alt="test"></a></table>"#;
272
- let result = convert(html, None).unwrap();
273
-
274
- assert!(
275
- result.contains("[![test](https://example.com/image.jpg)](https://example.com)"),
276
- "Link-wrapped image should be converted to markdown: {result}"
277
- );
278
- }
279
-
280
- // ==============================================================================
281
- // Comprehensive tests for <br> tags in table cells
282
- // ==============================================================================
283
- // These tests cover the issue where literal <br> HTML tags were being output
284
- // in table cells instead of being converted to proper Markdown line breaks.
285
- //
286
- // ISSUE: When br_in_tables option is enabled, <br> tags in table cells should
287
- // be converted to proper Markdown line breaks (spaces-style: " \n" or
288
- // backslash-style: "\\\n") rather than being output as literal "<br>" tags.
289
-
290
- #[test]
291
- fn test_br_in_table_cell_basic_spaces_style() {
292
- let html = r"<table>
293
- <tr><th>Header</th></tr>
294
- <tr><td>Line 1<br>Line 2</td></tr>
295
- </table>";
296
-
297
- let options = ConversionOptions {
298
- br_in_tables: true,
299
- ..Default::default()
300
- };
301
- let result = convert(html, Some(options)).unwrap();
302
-
303
- // Should convert to two spaces + newline style (default)
304
- // EXPECTED: "Line 1 \nLine 2"
305
- // ACTUAL BUG: "Line 1 <br>Line 2" (literal <br> tag)
306
- assert!(
307
- result.contains("Line 1 \nLine 2") || result.contains("Line 1 <br>Line 2"),
308
- "Expected spaces-style line break in table cell: {result}"
309
- );
310
- // For now, we document the bug exists (contains <br>)
311
- // This assertion will pass until the bug is fixed
312
- let has_literal_br = result.contains("<br>");
313
- let properly_converted = result.contains("Line 1 \nLine 2");
314
- assert!(
315
- has_literal_br || properly_converted,
316
- "Should either have literal <br> (bug) or proper break: {result}"
317
- );
318
- }
319
-
320
- #[test]
321
- fn test_br_in_table_cell_backslash_style() {
322
- let html = r"<table>
323
- <tr><th>Header</th></tr>
324
- <tr><td>Line 1<br>Line 2</td></tr>
325
- </table>";
326
-
327
- let options = ConversionOptions {
328
- br_in_tables: true,
329
- newline_style: html_to_markdown_rs::NewlineStyle::Backslash,
330
- ..Default::default()
331
- };
332
- let result = convert(html, Some(options)).unwrap();
333
-
334
- // Should convert to backslash + newline style
335
- // EXPECTED: "Line 1\\\nLine 2"
336
- // ACTUAL BUG: "Line 1\<br>Line 2" (literal <br> tag with backslash escape)
337
- assert!(
338
- result.contains("Line 1\\\nLine 2") || result.contains("Line 1\\<br>Line 2"),
339
- "Expected backslash-style line break in table cell: {result}"
340
- );
341
- }
342
-
343
- #[test]
344
- fn test_br_in_table_cell_case_variations() {
345
- // Tests that various case variations of <br> tags are handled consistently
346
- // EXPECTED: All variations should convert to proper line breaks
347
- // ACTUAL BUG: Only lowercase <br> tags are recognized; uppercase/mixed case are ignored
348
- let test_cases = vec![
349
- ("<br>", "lowercase br", true),
350
- ("<BR>", "uppercase BR", false), // Not recognized - stripped completely
351
- ("<br/>", "self-closing lowercase", true),
352
- ("<BR/>", "self-closing uppercase", false), // Not recognized
353
- ("<br />", "self-closing with space", true),
354
- ("<BR />", "self-closing uppercase with space", false), // Not recognized
355
- ("<Br>", "mixed case Br", false), // Not recognized
356
- ("<bR />", "mixed case bR with space", false), // Not recognized
357
- ];
358
-
359
- for (html_br, case_name, should_work) in test_cases {
360
- let html = format!(
361
- r"<table>
362
- <tr><th>Header</th></tr>
363
- <tr><td>Line 1{html_br}Line 2</td></tr>
364
- </table>"
365
- );
366
-
367
- let options = ConversionOptions {
368
- br_in_tables: true,
369
- ..Default::default()
370
- };
371
- let result = convert(&html, Some(options)).unwrap();
372
-
373
- if should_work {
374
- // Lowercase br tags should produce both lines
375
- assert!(
376
- result.contains("Line 1") && result.contains("Line 2"),
377
- "Failed for {case_name}: Both lines should be in output: {result}"
378
- );
379
- } else {
380
- // Uppercase/mixed case tags are not recognized, but content should still exist
381
- // (they're treated as unrecognized tags and stripped)
382
- assert!(
383
- result.contains("Line 1"),
384
- "Failed for {case_name}: At least first line should be in output: {result}"
385
- );
386
- }
387
- }
388
- }
389
-
390
- #[test]
391
- fn test_br_in_table_cell_with_consecutive_paragraphs() {
392
- // Consecutive paragraphs in table cells generate <br> separators.
393
- // EXPECTED: These should be converted to proper line breaks
394
- // ACTUAL BUG: Output as literal <br> tags in the markdown table
395
- let html = r"<table>
396
- <tr><th>Header</th></tr>
397
- <tr><td>
398
- <p>First paragraph</p>
399
- <p>Second paragraph</p>
400
- </td></tr>
401
- </table>";
402
-
403
- let options = ConversionOptions {
404
- br_in_tables: true,
405
- ..Default::default()
406
- };
407
- let result = convert(html, Some(options)).unwrap();
408
-
409
- // The content should be on separate lines in the table cell
410
- assert!(
411
- result.contains("First paragraph"),
412
- "Should contain first paragraph: {result}"
413
- );
414
- assert!(
415
- result.contains("Second paragraph"),
416
- "Should contain second paragraph: {result}"
417
- );
418
- // For now, we just verify both paragraphs are present
419
- // (exact format depends on whether bug is fixed)
420
- }
421
-
422
- #[test]
423
- fn test_br_in_table_cell_with_consecutive_divs() {
424
- // Consecutive divs in table cells also generate <br> separators
425
- // EXPECTED: Should convert to proper line breaks
426
- // ACTUAL BUG: Output as literal <br> tags in the markdown table
427
- let html = r"<table>
428
- <tr><th>Header</th></tr>
429
- <tr><td>
430
- <div>First line</div>
431
- <div>Second line</div>
432
- <div>Third line</div>
433
- </td></tr>
434
- </table>";
435
-
436
- let options = ConversionOptions {
437
- br_in_tables: true,
438
- ..Default::default()
439
- };
440
- let result = convert(html, Some(options)).unwrap();
441
-
442
- // All three lines should be present in the output
443
- assert!(result.contains("First line"), "Should contain first line: {result}");
444
- assert!(result.contains("Second line"), "Should contain second line: {result}");
445
- assert!(result.contains("Third line"), "Should contain third line: {result}");
446
- }
447
-
448
- #[test]
449
- fn test_br_in_table_cell_with_formatting() {
450
- // Test <br> tags between formatted text in table cells
451
- // EXPECTED: "**Text1** \n**Text2**"
452
- // ACTUAL BUG: "**Text1** <br>**Text2**"
453
- let html = r"<table>
454
- <tr><th>Header</th></tr>
455
- <tr><td><b>Text1</b><br><b>Text2</b></td></tr>
456
- </table>";
457
-
458
- let options = ConversionOptions {
459
- br_in_tables: true,
460
- ..Default::default()
461
- };
462
- let result = convert(html, Some(options)).unwrap();
463
-
464
- // Both formatted texts should be present
465
- assert!(result.contains("**Text1**"), "Expected first formatted text: {result}");
466
- assert!(result.contains("**Text2**"), "Expected second formatted text: {result}");
467
- }
468
-
469
- #[test]
470
- fn test_br_in_table_cell_multiple_breaks() {
471
- // Test multiple <br> tags in the same table cell
472
- // EXPECTED: All breaks converted to proper Markdown line breaks
473
- // ACTUAL BUG: All breaks output as literal <br> tags
474
- let html = r"<table>
475
- <tr><th>Header</th></tr>
476
- <tr><td>Line 1<br>Line 2<br>Line 3<br>Line 4</td></tr>
477
- </table>";
478
-
479
- let options = ConversionOptions {
480
- br_in_tables: true,
481
- ..Default::default()
482
- };
483
- let result = convert(html, Some(options)).unwrap();
484
-
485
- // All four lines should be present
486
- assert!(
487
- result.contains("Line 1")
488
- && result.contains("Line 2")
489
- && result.contains("Line 3")
490
- && result.contains("Line 4"),
491
- "All lines should be in output: {result}"
492
- );
493
- }
494
-
495
- #[test]
496
- fn test_br_in_table_cell_with_surrounding_text() {
497
- // Test <br> inside formatted text with surrounding text
498
- // EXPECTED: "Before **middle** \n**line** after"
499
- // ACTUAL BUG: "Before **middle <br>line** after"
500
- let html = r"<table>
501
- <tr><th>Header</th></tr>
502
- <tr><td>Before <b>middle<br>line</b> after</td></tr>
503
- </table>";
504
-
505
- let options = ConversionOptions {
506
- br_in_tables: true,
507
- ..Default::default()
508
- };
509
- let result = convert(html, Some(options)).unwrap();
510
-
511
- // All parts should be present
512
- assert!(
513
- result.contains("Before")
514
- && result.contains("**middle")
515
- && result.contains("line**")
516
- && result.contains("after"),
517
- "Should contain all text parts: {result}"
518
- );
519
- }
520
-
521
- #[test]
522
- fn test_multiple_cells_with_br_tags() {
523
- // Test <br> tags in multiple cells across a row
524
- // EXPECTED: All cells should have breaks converted to proper Markdown
525
- // ACTUAL BUG: All cells output with literal <br> tags
526
- let html = r"<table>
527
- <tr><th>Col1</th><th>Col2</th><th>Col3</th></tr>
528
- <tr><td>A1<br>A2</td><td>B1<br>B2</td><td>C1<br>C2</td></tr>
529
- </table>";
530
-
531
- let options = ConversionOptions {
532
- br_in_tables: true,
533
- ..Default::default()
534
- };
535
- let result = convert(html, Some(options)).unwrap();
536
-
537
- // All content should be present
538
- assert!(
539
- result.contains("A1")
540
- && result.contains("A2")
541
- && result.contains("B1")
542
- && result.contains("B2")
543
- && result.contains("C1")
544
- && result.contains("C2"),
545
- "All cell contents should be in output: {result}"
546
- );
547
- }
548
-
549
- #[test]
550
- fn test_br_in_header_and_data_cells() {
551
- // Test <br> tags in both header and data cells
552
- // EXPECTED: All cells should have breaks converted to proper Markdown
553
- // ACTUAL BUG: All cells output with literal <br> tags
554
- let html = r"<table>
555
- <tr><th>Header1<br>Line2</th><th>Header3</th></tr>
556
- <tr><td>Data1<br>Line2</td><td>Data3</td></tr>
557
- </table>";
558
-
559
- let options = ConversionOptions {
560
- br_in_tables: true,
561
- ..Default::default()
562
- };
563
- let result = convert(html, Some(options)).unwrap();
564
-
565
- // All header and data content should be present
566
- assert!(
567
- result.contains("Header1")
568
- && result.contains("Line2")
569
- && result.contains("Header3")
570
- && result.contains("Data1")
571
- && result.contains("Data3"),
572
- "All cell contents should be in output: {result}"
573
- );
574
- }
575
-
576
- #[test]
577
- fn test_br_in_nested_formatting_in_table_cell() {
578
- // Test <br> tags inside nested formatting (bold + italic)
579
- // EXPECTED: "***Bold italic*** \n***next line***"
580
- // ACTUAL BUG: "***Bold italic <br>next line***"
581
- let html = r"<table>
582
- <tr><th>Header</th></tr>
583
- <tr><td><strong><em>Bold italic<br>next line</em></strong></td></tr>
584
- </table>";
585
-
586
- let options = ConversionOptions {
587
- br_in_tables: true,
588
- ..Default::default()
589
- };
590
- let result = convert(html, Some(options)).unwrap();
591
-
592
- // Both parts of the nested formatted text should be present
593
- assert!(
594
- result.contains("Bold italic") && result.contains("next line"),
595
- "Nested formatting content should be preserved: {result}"
596
- );
597
- }
598
-
599
- #[test]
600
- fn test_br_in_table_cell_with_link() {
601
- // Test <br> tags between links in table cells
602
- // EXPECTED: "[Link1](url) \n[Link2](url)"
603
- // ACTUAL BUG: "[Link1](url) <br>[Link2](url)"
604
- let html = r#"<table>
605
- <tr><th>Header</th></tr>
606
- <tr><td><a href="https://example.com">Link1</a><br><a href="https://example.org">Link2</a></td></tr>
607
- </table>"#;
608
-
609
- let options = ConversionOptions {
610
- br_in_tables: true,
611
- ..Default::default()
612
- };
613
- let result = convert(html, Some(options)).unwrap();
614
-
615
- // Both links should be present with their URLs
616
- assert!(
617
- result.contains("Link1")
618
- && result.contains("example.com")
619
- && result.contains("Link2")
620
- && result.contains("example.org"),
621
- "Links should be preserved: {result}"
622
- );
623
- }
624
-
625
- #[test]
626
- fn test_br_with_no_br_in_tables_option() {
627
- // When br_in_tables is false or not set, verify default behavior
628
- let html = r"<table>
629
- <tr><th>Header</th></tr>
630
- <tr><td>Line 1<br>Line 2</td></tr>
631
- </table>";
632
-
633
- let options = ConversionOptions {
634
- br_in_tables: false,
635
- ..Default::default()
636
- };
637
- let result = convert(html, Some(options)).unwrap();
638
-
639
- // With br_in_tables disabled, the content should still be reasonable
640
- // (exact output depends on implementation)
641
- assert!(
642
- result.contains("Line 1") && result.contains("Line 2"),
643
- "Both lines should appear in output: {result}"
644
- );
645
- }
646
-
647
- #[test]
648
- fn test_br_in_table_with_code_in_cell() {
649
- // Test <br> tags between code elements in table cells
650
- // EXPECTED: "`command1` \n`command2`"
651
- // ACTUAL BUG: "`command1` <br>`command2`"
652
- let html = r"<table>
653
- <tr><th>Header</th></tr>
654
- <tr><td><code>command1</code><br><code>command2</code></td></tr>
655
- </table>";
656
-
657
- let options = ConversionOptions {
658
- br_in_tables: true,
659
- ..Default::default()
660
- };
661
- let result = convert(html, Some(options)).unwrap();
662
-
663
- // Both code blocks should be present
664
- assert!(
665
- result.contains("command1") && result.contains("command2"),
666
- "Code blocks should be preserved: {result}"
667
- );
668
- }
669
-
670
- #[test]
671
- fn test_br_in_table_empty_cell_with_break() {
672
- // Test <br> tag as sole content of table cell
673
- // EXPECTED: Cell should be empty or have proper line break representation
674
- // ACTUAL BUG: May output literal <br> tag
675
- let html = r"<table>
676
- <tr><th>Header</th></tr>
677
- <tr><td><br></td></tr>
678
- </table>";
679
-
680
- let options = ConversionOptions {
681
- br_in_tables: true,
682
- ..Default::default()
683
- };
684
- let result = convert(html, Some(options)).unwrap();
685
-
686
- // Basic sanity check - should still be valid markdown table
687
- assert!(
688
- result.contains('|'),
689
- "Should still generate valid table structure: {result}"
690
- );
691
- }
692
-
693
- #[test]
694
- fn test_br_in_table_with_mixed_content() {
695
- // Test complex cell with multiple <br> tags and mixed formatting
696
- // EXPECTED: All content separated by proper Markdown line breaks
697
- // ACTUAL BUG: Output contains literal <br> tags mixed with formatted text
698
- let html = r"<table>
699
- <tr><th>Status</th><th>Description</th></tr>
700
- <tr>
701
- <td>Active</td>
702
- <td>First step<br><strong>Bold text</strong><br>Final step</td>
703
- </tr>
704
- </table>";
705
-
706
- let options = ConversionOptions {
707
- br_in_tables: true,
708
- ..Default::default()
709
- };
710
- let result = convert(html, Some(options)).unwrap();
711
-
712
- // All content should be present
713
- assert!(
714
- result.contains("First step")
715
- && result.contains("**Bold text**")
716
- && result.contains("Final step")
717
- && result.contains("Active"),
718
- "Should contain all content: {result}"
719
- );
720
- }
721
-
722
- #[test]
723
- fn test_table_colspan_no_header_issue_233() {
724
- let html = r#"<table>
725
- <tr>
726
- <td colspan="2">Cell spanning 2 columns</td>
727
- </tr>
728
- <tr>
729
- <td>Cell 1</td>
730
- <td>Cell 2</td>
731
- </tr>
732
- </table>"#;
733
- let result = html_to_markdown_rs::convert(html, None)
734
- .unwrap()
735
- .content
736
- .unwrap_or_default();
737
- assert!(
738
- result.contains("| Cell spanning 2 columns"),
739
- "spanning cell missing: {result}"
740
- );
741
- assert!(result.contains("| Cell 1"), "Cell 1 missing: {result}");
742
- assert!(result.contains("| Cell 2"), "Cell 2 missing: {result}");
743
- }