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.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +347 -0
- data/Steepfile +10 -2
- data/ext/html_to_markdown_rb/Cargo.toml +3 -2
- data/ext/html_to_markdown_rb/extconf.rb +5 -5
- data/ext/html_to_markdown_rb/native/Cargo.lock +962 -0
- data/ext/html_to_markdown_rb/native/Cargo.toml +6 -11
- data/ext/html_to_markdown_rb/native/extconf.rb +14 -0
- data/ext/html_to_markdown_rb/src/lib.rs +1715 -646
- data/lib/html_to_markdown/native.rb +913 -37
- data/lib/html_to_markdown/version.rb +3 -3
- data/lib/html_to_markdown.rb +9 -4
- data/lib/html_to_markdown_rb.so +0 -0
- data/sig/types.rbs +59 -292
- metadata +32 -179
- data/ext/html_to_markdown_rb/Makefile +0 -592
- data/lib/bin/html-to-markdown +0 -0
- data/vendor/Cargo.toml +0 -33
- data/vendor/html-to-markdown-rs/Cargo.toml +0 -54
- data/vendor/html-to-markdown-rs/README.md +0 -278
- data/vendor/html-to-markdown-rs/examples/basic.rs +0 -24
- data/vendor/html-to-markdown-rs/examples/table.rs +0 -25
- data/vendor/html-to-markdown-rs/examples/test_deser.rs +0 -12
- data/vendor/html-to-markdown-rs/examples/test_escape.rs +0 -58
- data/vendor/html-to-markdown-rs/examples/test_inline_formatting.rs +0 -113
- data/vendor/html-to-markdown-rs/examples/test_lists.rs +0 -39
- data/vendor/html-to-markdown-rs/examples/test_semantic_tags.rs +0 -89
- data/vendor/html-to-markdown-rs/examples/test_tables.rs +0 -100
- data/vendor/html-to-markdown-rs/examples/test_task_lists.rs +0 -61
- data/vendor/html-to-markdown-rs/examples/test_whitespace.rs +0 -34
- data/vendor/html-to-markdown-rs/src/convert_api.rs +0 -349
- data/vendor/html-to-markdown-rs/src/converter/block/blockquote.rs +0 -178
- data/vendor/html-to-markdown-rs/src/converter/block/container.rs +0 -114
- data/vendor/html-to-markdown-rs/src/converter/block/div.rs +0 -149
- data/vendor/html-to-markdown-rs/src/converter/block/heading.rs +0 -428
- data/vendor/html-to-markdown-rs/src/converter/block/horizontal_rule.rs +0 -103
- data/vendor/html-to-markdown-rs/src/converter/block/line_break.rs +0 -89
- data/vendor/html-to-markdown-rs/src/converter/block/mod.rs +0 -10
- data/vendor/html-to-markdown-rs/src/converter/block/paragraph.rs +0 -140
- data/vendor/html-to-markdown-rs/src/converter/block/preformatted.rs +0 -298
- data/vendor/html-to-markdown-rs/src/converter/block/table/builder.rs +0 -453
- data/vendor/html-to-markdown-rs/src/converter/block/table/caption.rs +0 -44
- data/vendor/html-to-markdown-rs/src/converter/block/table/cell.rs +0 -276
- data/vendor/html-to-markdown-rs/src/converter/block/table/cells.rs +0 -336
- data/vendor/html-to-markdown-rs/src/converter/block/table/layout.rs +0 -58
- data/vendor/html-to-markdown-rs/src/converter/block/table/mod.rs +0 -266
- data/vendor/html-to-markdown-rs/src/converter/block/table/scanner.rs +0 -146
- data/vendor/html-to-markdown-rs/src/converter/block/table/utils.rs +0 -34
- data/vendor/html-to-markdown-rs/src/converter/block/unknown.rs +0 -138
- data/vendor/html-to-markdown-rs/src/converter/context.rs +0 -208
- data/vendor/html-to-markdown-rs/src/converter/dom_context.rs +0 -337
- data/vendor/html-to-markdown-rs/src/converter/form/elements.rs +0 -770
- data/vendor/html-to-markdown-rs/src/converter/form/mod.rs +0 -82
- data/vendor/html-to-markdown-rs/src/converter/format/djot.rs +0 -64
- data/vendor/html-to-markdown-rs/src/converter/format/markdown.rs +0 -59
- data/vendor/html-to-markdown-rs/src/converter/format/mod.rs +0 -43
- data/vendor/html-to-markdown-rs/src/converter/handlers/blockquote.rs +0 -173
- data/vendor/html-to-markdown-rs/src/converter/handlers/code_block.rs +0 -434
- data/vendor/html-to-markdown-rs/src/converter/handlers/graphic.rs +0 -234
- data/vendor/html-to-markdown-rs/src/converter/handlers/image.rs +0 -282
- data/vendor/html-to-markdown-rs/src/converter/handlers/link.rs +0 -316
- data/vendor/html-to-markdown-rs/src/converter/handlers/mod.rs +0 -26
- data/vendor/html-to-markdown-rs/src/converter/inline/code.rs +0 -306
- data/vendor/html-to-markdown-rs/src/converter/inline/emphasis.rs +0 -345
- data/vendor/html-to-markdown-rs/src/converter/inline/link.rs +0 -428
- data/vendor/html-to-markdown-rs/src/converter/inline/mod.rs +0 -237
- data/vendor/html-to-markdown-rs/src/converter/inline/ruby.rs +0 -337
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/marks.rs +0 -566
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/mod.rs +0 -86
- data/vendor/html-to-markdown-rs/src/converter/inline/semantic/typography.rs +0 -558
- data/vendor/html-to-markdown-rs/src/converter/list/definition.rs +0 -232
- data/vendor/html-to-markdown-rs/src/converter/list/item.rs +0 -332
- data/vendor/html-to-markdown-rs/src/converter/list/mod.rs +0 -70
- data/vendor/html-to-markdown-rs/src/converter/list/ordered.rs +0 -201
- data/vendor/html-to-markdown-rs/src/converter/list/unordered.rs +0 -195
- data/vendor/html-to-markdown-rs/src/converter/list/utils.rs +0 -314
- data/vendor/html-to-markdown-rs/src/converter/main.rs +0 -710
- data/vendor/html-to-markdown-rs/src/converter/main_helpers.rs +0 -452
- data/vendor/html-to-markdown-rs/src/converter/media/embedded.rs +0 -393
- data/vendor/html-to-markdown-rs/src/converter/media/graphic.rs +0 -4
- data/vendor/html-to-markdown-rs/src/converter/media/image.rs +0 -183
- data/vendor/html-to-markdown-rs/src/converter/media/mod.rs +0 -87
- data/vendor/html-to-markdown-rs/src/converter/media/svg.rs +0 -280
- data/vendor/html-to-markdown-rs/src/converter/metadata.rs +0 -220
- data/vendor/html-to-markdown-rs/src/converter/mod.rs +0 -156
- data/vendor/html-to-markdown-rs/src/converter/plain_text.rs +0 -516
- data/vendor/html-to-markdown-rs/src/converter/preprocessing_helpers.rs +0 -201
- data/vendor/html-to-markdown-rs/src/converter/reference_collector.rs +0 -69
- data/vendor/html-to-markdown-rs/src/converter/semantic/attributes.rs +0 -269
- data/vendor/html-to-markdown-rs/src/converter/semantic/definition_list.rs +0 -266
- data/vendor/html-to-markdown-rs/src/converter/semantic/figure.rs +0 -391
- data/vendor/html-to-markdown-rs/src/converter/semantic/mod.rs +0 -112
- data/vendor/html-to-markdown-rs/src/converter/semantic/sectioning.rs +0 -85
- data/vendor/html-to-markdown-rs/src/converter/semantic/summary.rs +0 -324
- data/vendor/html-to-markdown-rs/src/converter/text/mod.rs +0 -8
- data/vendor/html-to-markdown-rs/src/converter/text/processing.rs +0 -56
- data/vendor/html-to-markdown-rs/src/converter/text_node.rs +0 -269
- data/vendor/html-to-markdown-rs/src/converter/utility/attributes.rs +0 -151
- data/vendor/html-to-markdown-rs/src/converter/utility/caching.rs +0 -74
- data/vendor/html-to-markdown-rs/src/converter/utility/content.rs +0 -271
- data/vendor/html-to-markdown-rs/src/converter/utility/mod.rs +0 -17
- data/vendor/html-to-markdown-rs/src/converter/utility/preprocessing.rs +0 -1002
- data/vendor/html-to-markdown-rs/src/converter/utility/serialization.rs +0 -126
- data/vendor/html-to-markdown-rs/src/converter/utility/siblings.rs +0 -97
- data/vendor/html-to-markdown-rs/src/converter/visitor_hooks.rs +0 -189
- data/vendor/html-to-markdown-rs/src/error.rs +0 -43
- data/vendor/html-to-markdown-rs/src/exports.rs +0 -24
- data/vendor/html-to-markdown-rs/src/inline_images.rs +0 -336
- data/vendor/html-to-markdown-rs/src/lib.rs +0 -139
- data/vendor/html-to-markdown-rs/src/metadata/collector.rs +0 -457
- data/vendor/html-to-markdown-rs/src/metadata/config.rs +0 -394
- data/vendor/html-to-markdown-rs/src/metadata/extraction.rs +0 -398
- data/vendor/html-to-markdown-rs/src/metadata/mod.rs +0 -288
- data/vendor/html-to-markdown-rs/src/metadata/types.rs +0 -477
- data/vendor/html-to-markdown-rs/src/options/conversion.rs +0 -559
- data/vendor/html-to-markdown-rs/src/options/inline_image.rs +0 -111
- data/vendor/html-to-markdown-rs/src/options/mod.rs +0 -20
- data/vendor/html-to-markdown-rs/src/options/preprocessing.rs +0 -201
- data/vendor/html-to-markdown-rs/src/options/validation.rs +0 -416
- data/vendor/html-to-markdown-rs/src/prelude.rs +0 -1
- data/vendor/html-to-markdown-rs/src/rcdom.rs +0 -487
- data/vendor/html-to-markdown-rs/src/text.rs +0 -358
- data/vendor/html-to-markdown-rs/src/types/document.rs +0 -191
- data/vendor/html-to-markdown-rs/src/types/mod.rs +0 -17
- data/vendor/html-to-markdown-rs/src/types/result.rs +0 -54
- data/vendor/html-to-markdown-rs/src/types/structure_builder.rs +0 -791
- data/vendor/html-to-markdown-rs/src/types/structure_collector.rs +0 -483
- data/vendor/html-to-markdown-rs/src/types/tables.rs +0 -52
- data/vendor/html-to-markdown-rs/src/types/warnings.rs +0 -33
- data/vendor/html-to-markdown-rs/src/validation.rs +0 -158
- data/vendor/html-to-markdown-rs/src/visitor/default_impl.rs +0 -63
- data/vendor/html-to-markdown-rs/src/visitor/mod.rs +0 -41
- data/vendor/html-to-markdown-rs/src/visitor/traits.rs +0 -370
- data/vendor/html-to-markdown-rs/src/visitor/types.rs +0 -319
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/callbacks/mod.rs +0 -1
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/content.rs +0 -126
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/mod.rs +0 -27
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/state.rs +0 -110
- data/vendor/html-to-markdown-rs/src/visitor_helpers/helpers/traversal.rs +0 -250
- data/vendor/html-to-markdown-rs/src/visitor_helpers.rs +0 -597
- data/vendor/html-to-markdown-rs/src/wrapper/sync.rs +0 -413
- data/vendor/html-to-markdown-rs/src/wrapper/utils.rs +0 -290
- data/vendor/html-to-markdown-rs/src/wrapper.rs +0 -9
- data/vendor/html-to-markdown-rs/tests/br_in_inline_test.rs +0 -87
- data/vendor/html-to-markdown-rs/tests/commonmark_compliance_test.rs +0 -297
- data/vendor/html-to-markdown-rs/tests/djot_output_test.rs +0 -153
- data/vendor/html-to-markdown-rs/tests/exclude_selectors_test.rs +0 -132
- data/vendor/html-to-markdown-rs/tests/integration_test.rs +0 -631
- data/vendor/html-to-markdown-rs/tests/issue_121_regressions.rs +0 -49
- data/vendor/html-to-markdown-rs/tests/issue_127_regressions.rs +0 -58
- data/vendor/html-to-markdown-rs/tests/issue_128_regressions.rs +0 -17
- data/vendor/html-to-markdown-rs/tests/issue_131_regressions.rs +0 -41
- data/vendor/html-to-markdown-rs/tests/issue_134_regressions.rs +0 -40
- data/vendor/html-to-markdown-rs/tests/issue_139_regressions.rs +0 -26
- data/vendor/html-to-markdown-rs/tests/issue_140_regressions.rs +0 -185
- data/vendor/html-to-markdown-rs/tests/issue_143_regressions.rs +0 -100
- data/vendor/html-to-markdown-rs/tests/issue_145_regressions.rs +0 -133
- data/vendor/html-to-markdown-rs/tests/issue_146_regressions.rs +0 -144
- data/vendor/html-to-markdown-rs/tests/issue_176_regressions.rs +0 -62
- data/vendor/html-to-markdown-rs/tests/issue_190_regressions.rs +0 -128
- data/vendor/html-to-markdown-rs/tests/issue_199_regressions.rs +0 -20
- data/vendor/html-to-markdown-rs/tests/issue_200_regressions.rs +0 -62
- data/vendor/html-to-markdown-rs/tests/issue_212_regressions.rs +0 -68
- data/vendor/html-to-markdown-rs/tests/issue_216_217_regressions.rs +0 -87
- data/vendor/html-to-markdown-rs/tests/issue_336_regressions.rs +0 -74
- data/vendor/html-to-markdown-rs/tests/issue_339_regressions.rs +0 -92
- data/vendor/html-to-markdown-rs/tests/issue_347_regressions.rs +0 -154
- data/vendor/html-to-markdown-rs/tests/issue_348_visitor_plain.rs +0 -93
- data/vendor/html-to-markdown-rs/tests/json_ld_script_extraction.rs +0 -44
- data/vendor/html-to-markdown-rs/tests/lists_test.rs +0 -199
- data/vendor/html-to-markdown-rs/tests/plain_output_test.rs +0 -273
- data/vendor/html-to-markdown-rs/tests/preprocessing_tests.rs +0 -61
- data/vendor/html-to-markdown-rs/tests/reference_links_test.rs +0 -169
- data/vendor/html-to-markdown-rs/tests/sectioning_elements_test.rs +0 -137
- data/vendor/html-to-markdown-rs/tests/skip_images_test.rs +0 -522
- data/vendor/html-to-markdown-rs/tests/tables_test.rs +0 -743
- data/vendor/html-to-markdown-rs/tests/test_custom_elements.rs +0 -41
- data/vendor/html-to-markdown-rs/tests/test_issue_187.rs +0 -204
- data/vendor/html-to-markdown-rs/tests/test_issue_218.rs +0 -68
- data/vendor/html-to-markdown-rs/tests/test_issue_277.rs +0 -77
- data/vendor/html-to-markdown-rs/tests/test_max_depth.rs +0 -82
- data/vendor/html-to-markdown-rs/tests/test_nested_simple.rs +0 -45
- data/vendor/html-to-markdown-rs/tests/test_script_style_stripping.rs +0 -396
- data/vendor/html-to-markdown-rs/tests/test_spa_bisect.rs +0 -34
- data/vendor/html-to-markdown-rs/tests/visitor_code_integration_test.rs +0 -121
- data/vendor/html-to-markdown-rs/tests/visitor_integration_test.rs +0 -1190
- data/vendor/html-to-markdown-rs/tests/xml_tables_test.rs +0 -372
|
@@ -1,41 +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 std::fs;
|
|
11
|
-
use std::path::PathBuf;
|
|
12
|
-
|
|
13
|
-
use html_to_markdown_rs::ConversionOptions;
|
|
14
|
-
|
|
15
|
-
fn fixture_path(name: &str) -> PathBuf {
|
|
16
|
-
[env!("CARGO_MANIFEST_DIR"), "../../test_documents/html/issues", name]
|
|
17
|
-
.iter()
|
|
18
|
-
.collect()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
fn default_options() -> ConversionOptions {
|
|
22
|
-
ConversionOptions {
|
|
23
|
-
extract_metadata: false,
|
|
24
|
-
autolinks: false,
|
|
25
|
-
..Default::default()
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#[test]
|
|
30
|
-
fn test_custom_elements() {
|
|
31
|
-
let html = fs::read_to_string(fixture_path("test-with-custom-elements.html")).expect("read html");
|
|
32
|
-
|
|
33
|
-
eprintln!("HTML: {html}");
|
|
34
|
-
|
|
35
|
-
let result = convert(&html, Some(default_options())).expect("convert html");
|
|
36
|
-
eprintln!("Result: {result}");
|
|
37
|
-
|
|
38
|
-
assert!(result.contains("Team Appraisal"));
|
|
39
|
-
assert!(result.contains("Team"));
|
|
40
|
-
assert!(result.contains("Pending"));
|
|
41
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
//! Test to reproduce issue #187: `visit_div` is not executed
|
|
2
|
-
//!
|
|
3
|
-
//! This test demonstrates that:
|
|
4
|
-
//! 1. There is NO `visit_div`, `visit_script`, `visit_style` method
|
|
5
|
-
//! 2. Instead, users should use `visit_element_start` with `tag_name` filtering
|
|
6
|
-
//! 3. The documentation incorrectly shows these non-existent methods
|
|
7
|
-
|
|
8
|
-
#![cfg(feature = "visitor")]
|
|
9
|
-
|
|
10
|
-
use html_to_markdown_rs::visitor::{HtmlVisitor, NodeContext, VisitResult, VisitorHandle};
|
|
11
|
-
use html_to_markdown_rs::{ConversionError, ConversionOptions, ConversionResult};
|
|
12
|
-
use std::cell::RefCell;
|
|
13
|
-
use std::rc::Rc;
|
|
14
|
-
|
|
15
|
-
fn convert(
|
|
16
|
-
html: &str,
|
|
17
|
-
options: Option<ConversionOptions>,
|
|
18
|
-
visitor: Option<VisitorHandle>,
|
|
19
|
-
) -> Result<ConversionResult, ConversionError> {
|
|
20
|
-
let mut opts = options.unwrap_or_default();
|
|
21
|
-
if visitor.is_some() {
|
|
22
|
-
opts.visitor = visitor;
|
|
23
|
-
}
|
|
24
|
-
html_to_markdown_rs::convert(html, Some(opts))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/// This is what the documentation SHOWS (but doesn't work)
|
|
28
|
-
/// The methods `visit_div`, `visit_script`, `visit_style` DO NOT EXIST
|
|
29
|
-
#[allow(dead_code)]
|
|
30
|
-
#[derive(Debug, Default)]
|
|
31
|
-
struct DocumentedButBrokenVisitor {
|
|
32
|
-
skipped_elements: Vec<(String, String)>,
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// NOTE: These methods will NEVER be called because they don't exist in HtmlVisitor trait!
|
|
36
|
-
// impl DocumentedButBrokenVisitor {
|
|
37
|
-
// fn visit_div(&mut self, ctx: &NodeContext, _content: &str) -> VisitResult {
|
|
38
|
-
// // This will NEVER be called!
|
|
39
|
-
// let classes = ctx.attributes.get("class").map(|s| s.as_str()).unwrap_or("");
|
|
40
|
-
// if classes.contains("ad") || classes.contains("advertisement") {
|
|
41
|
-
// self.skipped_elements.push(("div".to_string(), classes.to_string()));
|
|
42
|
-
// return VisitResult::Skip;
|
|
43
|
-
// }
|
|
44
|
-
// VisitResult::Continue
|
|
45
|
-
// }
|
|
46
|
-
//
|
|
47
|
-
// fn visit_script(&mut self, ctx: &NodeContext) -> VisitResult {
|
|
48
|
-
// // This will NEVER be called!
|
|
49
|
-
// self.skipped_elements.push(("script".to_string(), "".to_string()));
|
|
50
|
-
// VisitResult::Skip
|
|
51
|
-
// }
|
|
52
|
-
// }
|
|
53
|
-
|
|
54
|
-
/// This is the CORRECT way to filter divs, scripts, and styles
|
|
55
|
-
#[derive(Debug, Default)]
|
|
56
|
-
struct ContentFilter {
|
|
57
|
-
skipped_elements: Vec<(String, String)>,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
impl HtmlVisitor for ContentFilter {
|
|
61
|
-
/// Use `visit_element_start` to filter ANY element by tag name
|
|
62
|
-
fn visit_element_start(&mut self, ctx: &NodeContext) -> VisitResult {
|
|
63
|
-
let tag_name = ctx.tag_name.as_str();
|
|
64
|
-
|
|
65
|
-
match tag_name {
|
|
66
|
-
"div" => {
|
|
67
|
-
// Filter divs with unwanted classes
|
|
68
|
-
let classes = ctx.attributes.get("class").map_or("", std::string::String::as_str);
|
|
69
|
-
if classes.contains("ad")
|
|
70
|
-
|| classes.contains("advertisement")
|
|
71
|
-
|| classes.contains("tracking")
|
|
72
|
-
|| classes.contains("analytics")
|
|
73
|
-
{
|
|
74
|
-
self.skipped_elements.push(("div".to_string(), classes.to_string()));
|
|
75
|
-
return VisitResult::Skip;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
"script" => {
|
|
79
|
-
// Always remove script tags
|
|
80
|
-
self.skipped_elements.push(("script".to_string(), String::new()));
|
|
81
|
-
return VisitResult::Skip;
|
|
82
|
-
}
|
|
83
|
-
"style" => {
|
|
84
|
-
// Always remove style tags
|
|
85
|
-
self.skipped_elements.push(("style".to_string(), String::new()));
|
|
86
|
-
return VisitResult::Skip;
|
|
87
|
-
}
|
|
88
|
-
_ => {}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
VisitResult::Continue
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/// Still use specific methods for links and images
|
|
95
|
-
fn visit_image(&mut self, ctx: &NodeContext, src: &str, _alt: &str, _title: Option<&str>) -> VisitResult {
|
|
96
|
-
// Remove tracking pixels (1x1 images)
|
|
97
|
-
let width = ctx.attributes.get("width").map_or("", std::string::String::as_str);
|
|
98
|
-
let height = ctx.attributes.get("height").map_or("", std::string::String::as_str);
|
|
99
|
-
|
|
100
|
-
if width == "1" && height == "1" {
|
|
101
|
-
self.skipped_elements
|
|
102
|
-
.push(("img".to_string(), format!("tracking pixel: {src}")));
|
|
103
|
-
return VisitResult::Skip;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Skip images with "tracking" or "analytics" in the URL
|
|
107
|
-
if src.to_lowercase().contains("tracking") || src.to_lowercase().contains("analytics") {
|
|
108
|
-
self.skipped_elements
|
|
109
|
-
.push(("img".to_string(), format!("tracking URL: {src}")));
|
|
110
|
-
return VisitResult::Skip;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
VisitResult::Continue
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
fn visit_link(&mut self, _ctx: &NodeContext, href: &str, text: &str, _title: Option<&str>) -> VisitResult {
|
|
117
|
-
// Remove links with utm_* tracking parameters
|
|
118
|
-
if href.to_lowercase().contains("utm_") {
|
|
119
|
-
// Strip tracking params but keep the link
|
|
120
|
-
if let Some(base_url) = href.split('?').next() {
|
|
121
|
-
return VisitResult::Custom(format!("[{text}]({base_url})"));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
VisitResult::Continue
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
#[test]
|
|
130
|
-
fn test_issue_187_content_filter() {
|
|
131
|
-
let html = r#"
|
|
132
|
-
<article>
|
|
133
|
-
<h1>Blog Post Title</h1>
|
|
134
|
-
<p>This is the main content of the article.</p>
|
|
135
|
-
|
|
136
|
-
<div class="ad advertisement">
|
|
137
|
-
<p>This is an advertisement block that should be removed.</p>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
<p>More content here.</p>
|
|
141
|
-
|
|
142
|
-
<img src="https://tracking.example.com/pixel.gif" width="1" height="1" alt="">
|
|
143
|
-
|
|
144
|
-
<div class="content">
|
|
145
|
-
<p>Legitimate content in a div.</p>
|
|
146
|
-
<img src="https://cdn.example.com/image.jpg" alt="Article image" width="800">
|
|
147
|
-
</div>
|
|
148
|
-
|
|
149
|
-
<script>
|
|
150
|
-
console.log("This script should be removed");
|
|
151
|
-
</script>
|
|
152
|
-
|
|
153
|
-
<p>Read more on <a href="https://example.com/article?utm_source=newsletter&utm_medium=email">our website</a>.</p>
|
|
154
|
-
|
|
155
|
-
<div class="tracking analytics">
|
|
156
|
-
<img src="https://analytics.example.com/track.png" alt="">
|
|
157
|
-
</div>
|
|
158
|
-
</article>
|
|
159
|
-
"#;
|
|
160
|
-
|
|
161
|
-
let visitor = Rc::new(RefCell::new(ContentFilter::default()));
|
|
162
|
-
let result = convert(html, None, Some(visitor.clone()))
|
|
163
|
-
.unwrap()
|
|
164
|
-
.content
|
|
165
|
-
.unwrap_or_default();
|
|
166
|
-
|
|
167
|
-
println!("Converted Markdown:\n{result}");
|
|
168
|
-
println!("\nSkipped Elements:");
|
|
169
|
-
for (tag, info) in &visitor.borrow().skipped_elements {
|
|
170
|
-
println!("- {tag}: {info}");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Verify that unwanted content was filtered out
|
|
174
|
-
assert!(
|
|
175
|
-
!result.contains("advertisement block that should be removed"),
|
|
176
|
-
"Ad div should be removed"
|
|
177
|
-
);
|
|
178
|
-
assert!(!result.contains("console.log"), "Script tag should be removed");
|
|
179
|
-
|
|
180
|
-
// Verify that legitimate content remains
|
|
181
|
-
assert!(result.contains("Blog Post Title"), "Heading should be preserved");
|
|
182
|
-
assert!(result.contains("main content"), "Main content should be preserved");
|
|
183
|
-
assert!(result.contains("Legitimate content"), "Content div should be preserved");
|
|
184
|
-
assert!(result.contains("Article image"), "Legitimate image should be preserved");
|
|
185
|
-
|
|
186
|
-
// Verify tracking parameters were stripped from link
|
|
187
|
-
assert!(
|
|
188
|
-
result.contains("[our website](https://example.com/article)"),
|
|
189
|
-
"Link tracking params should be stripped"
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
// Verify skipped elements were tracked
|
|
193
|
-
let borrowed = visitor.borrow();
|
|
194
|
-
assert!(
|
|
195
|
-
borrowed.skipped_elements.iter().any(|(tag, _)| tag == "div"),
|
|
196
|
-
"Should have skipped ad divs"
|
|
197
|
-
);
|
|
198
|
-
// Note: script and style tags are stripped during preprocessing before the visitor sees them,
|
|
199
|
-
// so they won't appear in skipped_elements. Only the visitor can control div, img, etc.
|
|
200
|
-
assert!(
|
|
201
|
-
borrowed.skipped_elements.iter().any(|(tag, _)| tag == "img"),
|
|
202
|
-
"Should have skipped tracking pixel"
|
|
203
|
-
);
|
|
204
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
//! Test to reproduce issue #218: Rust panic with Cyrillic HTML
|
|
2
|
-
//!
|
|
3
|
-
//! When converting HTML containing multi-byte UTF-8 characters (e.g., Cyrillic)
|
|
4
|
-
//! with tabs between block elements and any visitor, a panic occurs:
|
|
5
|
-
//! "byte index N is not a char boundary"
|
|
6
|
-
|
|
7
|
-
#![cfg(feature = "visitor")]
|
|
8
|
-
|
|
9
|
-
use std::cell::RefCell;
|
|
10
|
-
use std::rc::Rc;
|
|
11
|
-
|
|
12
|
-
use html_to_markdown_rs::visitor::{HtmlVisitor, VisitorHandle};
|
|
13
|
-
use html_to_markdown_rs::{ConversionError, ConversionOptions, ConversionResult};
|
|
14
|
-
|
|
15
|
-
fn convert(
|
|
16
|
-
html: &str,
|
|
17
|
-
options: Option<ConversionOptions>,
|
|
18
|
-
visitor: Option<VisitorHandle>,
|
|
19
|
-
) -> Result<ConversionResult, ConversionError> {
|
|
20
|
-
let mut opts = options.unwrap_or_default();
|
|
21
|
-
if visitor.is_some() {
|
|
22
|
-
opts.visitor = visitor;
|
|
23
|
-
}
|
|
24
|
-
html_to_markdown_rs::convert(html, Some(opts))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/// Empty visitor — does nothing, just uses default implementations.
|
|
28
|
-
#[derive(Debug, Default)]
|
|
29
|
-
struct EmptyVisitor;
|
|
30
|
-
|
|
31
|
-
impl HtmlVisitor for EmptyVisitor {}
|
|
32
|
-
|
|
33
|
-
fn make_visitor() -> Rc<RefCell<dyn HtmlVisitor>> {
|
|
34
|
-
Rc::new(RefCell::new(EmptyVisitor))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#[test]
|
|
38
|
-
fn test_cyrillic_with_tabs_between_divs_and_visitor() {
|
|
39
|
-
// Exact reproduction from the issue
|
|
40
|
-
let html = "<div><span>А</span></div>\t\t\t<div><span>По";
|
|
41
|
-
let result = convert(html, None, Some(make_visitor()));
|
|
42
|
-
assert!(result.is_ok(), "Should not panic: {result:?}");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
#[test]
|
|
46
|
-
fn test_multibyte_utf8_with_tabs_and_visitor() {
|
|
47
|
-
let cases = [
|
|
48
|
-
"<div><span>日本語</span></div>\t\t\t<div><span>テスト",
|
|
49
|
-
"<div><span>한국어</span></div>\t\t\t<div><span>테스트",
|
|
50
|
-
"<div><span>Привет</span></div>\t\t\t<div><span>Мир",
|
|
51
|
-
"<div><span>🎉</span></div>\t\t\t<div><span>🚀",
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
for html in &cases {
|
|
55
|
-
let result = convert(html, None, Some(make_visitor()));
|
|
56
|
-
assert!(result.is_ok(), "Should not panic for: {html}\nError: {result:?}");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
#[test]
|
|
61
|
-
fn test_cyrillic_with_varying_tab_counts_and_visitor() {
|
|
62
|
-
for n in 1..=5 {
|
|
63
|
-
let tabs = "\t".repeat(n);
|
|
64
|
-
let html = format!("<div><span>А</span></div>{tabs}<div><span>По");
|
|
65
|
-
let result = convert(&html, None, Some(make_visitor()));
|
|
66
|
-
assert!(result.is_ok(), "Should not panic with {n} tabs: {result:?}");
|
|
67
|
-
}
|
|
68
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#![allow(missing_docs)]
|
|
2
|
-
|
|
3
|
-
//! Regression test for issue #277: silent truncation on large HTML inputs.
|
|
4
|
-
//!
|
|
5
|
-
//! The bug was caused by `repair_with_html5ever` re-introducing `<script>` elements
|
|
6
|
-
//! that had already been stripped, and `preprocess_html` failing to find the closing
|
|
7
|
-
//! tag when script content contained unbalanced literal `<script>` strings.
|
|
8
|
-
|
|
9
|
-
fn convert(html: &str) -> String {
|
|
10
|
-
html_to_markdown_rs::convert(html, None)
|
|
11
|
-
.expect("conversion should not fail")
|
|
12
|
-
.content
|
|
13
|
-
.unwrap_or_default()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/// When custom elements trigger html5ever repair, scripts must be re-stripped.
|
|
17
|
-
/// Without the fix, content after a script with unbalanced `<script>` literals
|
|
18
|
-
/// would be silently truncated.
|
|
19
|
-
#[test]
|
|
20
|
-
fn test_no_truncation_after_repair_with_scripts() {
|
|
21
|
-
// Custom element triggers repair_with_html5ever
|
|
22
|
-
// Script content has an unbalanced literal `<script>` that confuses depth tracking
|
|
23
|
-
let html = r"<html>
|
|
24
|
-
<head>
|
|
25
|
-
<script>
|
|
26
|
-
var example = '<script>';
|
|
27
|
-
console.log(example);
|
|
28
|
-
</script>
|
|
29
|
-
</head>
|
|
30
|
-
<body>
|
|
31
|
-
<custom-widget>widget</custom-widget>
|
|
32
|
-
<p>Content before</p>
|
|
33
|
-
<p>Content after scripts that must not be truncated</p>
|
|
34
|
-
<p>Final paragraph</p>
|
|
35
|
-
</body>
|
|
36
|
-
</html>";
|
|
37
|
-
|
|
38
|
-
let result = convert(html);
|
|
39
|
-
assert!(
|
|
40
|
-
result.contains("Content before"),
|
|
41
|
-
"Should contain content before script region"
|
|
42
|
-
);
|
|
43
|
-
assert!(
|
|
44
|
-
result.contains("Content after scripts that must not be truncated"),
|
|
45
|
-
"Content after scripts should NOT be silently truncated. Got:\n{result}"
|
|
46
|
-
);
|
|
47
|
-
assert!(
|
|
48
|
-
result.contains("Final paragraph"),
|
|
49
|
-
"Final content should be present. Got:\n{result}"
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// Ensure `preprocess_html` doesn't truncate the rest of the document when
|
|
54
|
-
/// `find_closing_tag` returns None (unmatched script opening).
|
|
55
|
-
#[test]
|
|
56
|
-
fn test_preprocess_unmatched_script_preserves_remaining_content() {
|
|
57
|
-
// Even without custom elements, preprocess_html's unwrap_or fallback
|
|
58
|
-
// should not consume the entire rest of the document.
|
|
59
|
-
let html = r"<html><body>
|
|
60
|
-
<p>Before</p>
|
|
61
|
-
<script>var x = '<script>'; var y = '<script>';</script>
|
|
62
|
-
<p>After first script</p>
|
|
63
|
-
<script>var z = 1;</script>
|
|
64
|
-
<p>After second script</p>
|
|
65
|
-
</body></html>";
|
|
66
|
-
|
|
67
|
-
let result = convert(html);
|
|
68
|
-
assert!(result.contains("Before"), "Content before scripts should be present");
|
|
69
|
-
assert!(
|
|
70
|
-
result.contains("After first script"),
|
|
71
|
-
"Content after first script should be present. Got:\n{result}"
|
|
72
|
-
);
|
|
73
|
-
assert!(
|
|
74
|
-
result.contains("After second script"),
|
|
75
|
-
"Content after second script should be present. Got:\n{result}"
|
|
76
|
-
);
|
|
77
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#![allow(missing_docs)]
|
|
2
|
-
|
|
3
|
-
//! Tests for the `max_depth` recursion-safety option.
|
|
4
|
-
|
|
5
|
-
use html_to_markdown_rs::ConversionOptions;
|
|
6
|
-
|
|
7
|
-
fn convert_with_options(html: &str, options: ConversionOptions) -> String {
|
|
8
|
-
html_to_markdown_rs::convert(html, Some(options))
|
|
9
|
-
.expect("conversion should not fail")
|
|
10
|
-
.content
|
|
11
|
-
.unwrap_or_default()
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/// With the default `max_depth: None`, deeply nested content should be fully converted.
|
|
15
|
-
#[test]
|
|
16
|
-
fn test_max_depth_none_converts_deeply_nested() {
|
|
17
|
-
// Build 100 levels of nesting around a leaf text node.
|
|
18
|
-
let mut html = String::from("<p>deep</p>");
|
|
19
|
-
for _ in 0..100 {
|
|
20
|
-
html = format!("<div>{html}</div>");
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let options = ConversionOptions {
|
|
24
|
-
extract_metadata: false,
|
|
25
|
-
max_depth: None,
|
|
26
|
-
..Default::default()
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
let result = convert_with_options(&html, options);
|
|
30
|
-
assert!(
|
|
31
|
-
result.contains("deep"),
|
|
32
|
-
"Deeply nested text should be present when max_depth is None. Got:\n{result}"
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/// With `max_depth: Some(2)`, block elements at depth 2 are not visited, so
|
|
37
|
-
/// their text content is excluded from the output.
|
|
38
|
-
#[test]
|
|
39
|
-
fn test_max_depth_truncates_at_limit() {
|
|
40
|
-
// Depth counting (each handler passes depth+1 to its children):
|
|
41
|
-
// depth 0: outer <div> — visited
|
|
42
|
-
// depth 1: <p> — visited, paragraph handler passes depth+1 to children
|
|
43
|
-
// depth 2: "shallow" — visited (2 < 3), appears in output
|
|
44
|
-
// depth 1: inner <div> — visited, div handler passes depth+1 to children
|
|
45
|
-
// depth 2: <p> — visited, paragraph handler passes depth+1 to children
|
|
46
|
-
// depth 3: "deep" — skipped (3 >= 3), absent from output
|
|
47
|
-
let html = "<div><p>shallow</p><div><p>deep</p></div></div>";
|
|
48
|
-
|
|
49
|
-
let options = ConversionOptions {
|
|
50
|
-
extract_metadata: false,
|
|
51
|
-
max_depth: Some(3),
|
|
52
|
-
..Default::default()
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
let result = convert_with_options(html, options);
|
|
56
|
-
assert!(
|
|
57
|
-
result.contains("shallow"),
|
|
58
|
-
"Content at depth < max_depth should be present. Got:\n{result}"
|
|
59
|
-
);
|
|
60
|
-
assert!(
|
|
61
|
-
!result.contains("deep"),
|
|
62
|
-
"Content at depth >= max_depth should be absent. Got:\n{result}"
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/// With `max_depth: Some(0)`, no nodes are processed and the output is empty or whitespace only.
|
|
67
|
-
#[test]
|
|
68
|
-
fn test_max_depth_zero_produces_empty() {
|
|
69
|
-
let html = "<p>hello</p>";
|
|
70
|
-
|
|
71
|
-
let options = ConversionOptions {
|
|
72
|
-
extract_metadata: false,
|
|
73
|
-
max_depth: Some(0),
|
|
74
|
-
..Default::default()
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
let result = convert_with_options(html, options);
|
|
78
|
-
assert!(
|
|
79
|
-
result.trim().is_empty(),
|
|
80
|
-
"max_depth: Some(0) should produce no output. Got:\n{result}"
|
|
81
|
-
);
|
|
82
|
-
}
|
|
@@ -1,45 +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 std::fs;
|
|
11
|
-
use std::path::PathBuf;
|
|
12
|
-
|
|
13
|
-
use html_to_markdown_rs::ConversionOptions;
|
|
14
|
-
|
|
15
|
-
fn fixture_path(name: &str) -> PathBuf {
|
|
16
|
-
[env!("CARGO_MANIFEST_DIR"), "../../test_documents/html/issues", name]
|
|
17
|
-
.iter()
|
|
18
|
-
.collect()
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
fn default_options() -> ConversionOptions {
|
|
22
|
-
ConversionOptions {
|
|
23
|
-
extract_metadata: false,
|
|
24
|
-
autolinks: false,
|
|
25
|
-
..Default::default()
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
fn normalize_newlines(input: &str) -> String {
|
|
30
|
-
input.replace("\r\n", "\n").replace('\r', "\n")
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
#[test]
|
|
34
|
-
fn test_nested_simple() {
|
|
35
|
-
let html = fs::read_to_string(fixture_path("test-nested-simple.html")).expect("read html");
|
|
36
|
-
let expected = fs::read_to_string(fixture_path("test-nested-simple.md")).expect("read markdown");
|
|
37
|
-
|
|
38
|
-
eprintln!("HTML: {html}");
|
|
39
|
-
eprintln!("Expected: {expected}");
|
|
40
|
-
|
|
41
|
-
let result = convert(&html, Some(default_options())).expect("convert html");
|
|
42
|
-
eprintln!("Result: {result}");
|
|
43
|
-
|
|
44
|
-
assert_eq!(normalize_newlines(&result), normalize_newlines(&expected));
|
|
45
|
-
}
|