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,158 +0,0 @@
|
|
|
1
|
-
//! Input validation module for HTML to Markdown conversion.
|
|
2
|
-
//!
|
|
3
|
-
//! Provides validation functions to detect and reject binary data,
|
|
4
|
-
//! corrupted input, and other non-text content.
|
|
5
|
-
|
|
6
|
-
use crate::error::{ConversionError, Result};
|
|
7
|
-
|
|
8
|
-
const BINARY_SCAN_LIMIT: usize = 8192;
|
|
9
|
-
const BINARY_CONTROL_RATIO: f64 = 0.3;
|
|
10
|
-
const BINARY_UTF16_NULL_RATIO: f64 = 0.2;
|
|
11
|
-
const BINARY_NUL_RATIO: f64 = 0.01;
|
|
12
|
-
const BINARY_NUL_MAX: usize = 8;
|
|
13
|
-
|
|
14
|
-
const BINARY_MAGIC_PREFIXES: &[(&[u8], &str)] = &[
|
|
15
|
-
(b"\x1F\x8B", "gzip-compressed data"),
|
|
16
|
-
(b"\x28\xB5\x2F\xFD", "zstd-compressed data"),
|
|
17
|
-
(b"PK\x03\x04", "zip archive"),
|
|
18
|
-
(b"PK\x05\x06", "zip archive"),
|
|
19
|
-
(b"PK\x07\x08", "zip archive"),
|
|
20
|
-
(b"%PDF-", "PDF data"),
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
24
|
-
pub enum Utf16Encoding {
|
|
25
|
-
BomLe,
|
|
26
|
-
BomBe,
|
|
27
|
-
NoBomLe,
|
|
28
|
-
NoBomBe,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/// Validate HTML input and reject binary/corrupted data.
|
|
32
|
-
///
|
|
33
|
-
/// # Errors
|
|
34
|
-
///
|
|
35
|
-
/// Returns `ConversionError::InvalidInput` if the input is detected as binary,
|
|
36
|
-
/// compressed, or contains too many control characters.
|
|
37
|
-
#[allow(clippy::cast_precision_loss)]
|
|
38
|
-
pub fn validate_input(html: &str) -> Result<()> {
|
|
39
|
-
let bytes = html.as_bytes();
|
|
40
|
-
if bytes.is_empty() {
|
|
41
|
-
return Ok(());
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if let Some(label) = detect_binary_magic(bytes) {
|
|
45
|
-
return Err(ConversionError::InvalidInput(format!(
|
|
46
|
-
"binary data detected ({label}); decode/decompress to UTF-8 HTML first"
|
|
47
|
-
)));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let sample_len = bytes.len().min(BINARY_SCAN_LIMIT);
|
|
51
|
-
let mut control_count = 0usize;
|
|
52
|
-
let mut nul_count = 0usize;
|
|
53
|
-
|
|
54
|
-
for &byte in &bytes[..sample_len] {
|
|
55
|
-
if byte == 0 {
|
|
56
|
-
nul_count += 1;
|
|
57
|
-
}
|
|
58
|
-
let is_control = (byte < 0x09) || (0x0E..0x20).contains(&byte);
|
|
59
|
-
if is_control {
|
|
60
|
-
control_count += 1;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if nul_count > 0 {
|
|
65
|
-
if let Some(encoding) = detect_utf16_encoding(bytes) {
|
|
66
|
-
let label = utf16_label(encoding);
|
|
67
|
-
return Err(ConversionError::InvalidInput(format!(
|
|
68
|
-
"binary data detected ({label}); decode to UTF-8 HTML first"
|
|
69
|
-
)));
|
|
70
|
-
}
|
|
71
|
-
let nul_ratio = nul_count as f64 / sample_len as f64;
|
|
72
|
-
if nul_ratio > BINARY_NUL_RATIO && nul_count > BINARY_NUL_MAX {
|
|
73
|
-
return Err(ConversionError::InvalidInput(
|
|
74
|
-
"binary data detected (excess NUL bytes)".to_string(),
|
|
75
|
-
));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if sample_len >= 32 {
|
|
80
|
-
let control_ratio = control_count as f64 / sample_len as f64;
|
|
81
|
-
if control_ratio > BINARY_CONTROL_RATIO {
|
|
82
|
-
return Err(ConversionError::InvalidInput(
|
|
83
|
-
"binary data detected (excess control bytes)".to_string(),
|
|
84
|
-
));
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
Ok(())
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
fn detect_binary_magic(bytes: &[u8]) -> Option<&'static str> {
|
|
92
|
-
for (prefix, label) in BINARY_MAGIC_PREFIXES {
|
|
93
|
-
if bytes.starts_with(prefix) {
|
|
94
|
-
return Some(*label);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
None
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#[allow(clippy::cast_precision_loss)]
|
|
101
|
-
pub fn detect_utf16_encoding(bytes: &[u8]) -> Option<Utf16Encoding> {
|
|
102
|
-
if bytes.len() >= 2 {
|
|
103
|
-
if bytes.starts_with(b"\xFF\xFE") {
|
|
104
|
-
return Some(Utf16Encoding::BomLe);
|
|
105
|
-
}
|
|
106
|
-
if bytes.starts_with(b"\xFE\xFF") {
|
|
107
|
-
return Some(Utf16Encoding::BomBe);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
let sample_len = bytes.len().min(BINARY_SCAN_LIMIT);
|
|
112
|
-
if sample_len < 4 {
|
|
113
|
-
return None;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
let mut nul_count = 0usize;
|
|
117
|
-
let mut even_nul_count = 0usize;
|
|
118
|
-
let mut odd_nul_count = 0usize;
|
|
119
|
-
|
|
120
|
-
for (idx, &byte) in bytes[..sample_len].iter().enumerate() {
|
|
121
|
-
if byte == 0 {
|
|
122
|
-
nul_count += 1;
|
|
123
|
-
if idx % 2 == 0 {
|
|
124
|
-
even_nul_count += 1;
|
|
125
|
-
} else {
|
|
126
|
-
odd_nul_count += 1;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if nul_count < 2 {
|
|
132
|
-
return None;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let nul_ratio = nul_count as f64 / sample_len as f64;
|
|
136
|
-
if nul_ratio < BINARY_UTF16_NULL_RATIO {
|
|
137
|
-
return None;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
let dominant_ratio = (even_nul_count.max(odd_nul_count) as f64) / nul_count as f64;
|
|
141
|
-
if dominant_ratio < 0.9 {
|
|
142
|
-
return None;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if odd_nul_count >= even_nul_count {
|
|
146
|
-
Some(Utf16Encoding::NoBomLe)
|
|
147
|
-
} else {
|
|
148
|
-
Some(Utf16Encoding::NoBomBe)
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
fn utf16_label(encoding: Utf16Encoding) -> &'static str {
|
|
153
|
-
match encoding {
|
|
154
|
-
Utf16Encoding::BomLe => "UTF-16LE BOM",
|
|
155
|
-
Utf16Encoding::BomBe => "UTF-16BE BOM",
|
|
156
|
-
Utf16Encoding::NoBomLe | Utf16Encoding::NoBomBe => "UTF-16 data without BOM",
|
|
157
|
-
}
|
|
158
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
//! Default visitor implementations and utilities.
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides standard visitor patterns and helpers for common use cases.
|
|
4
|
-
|
|
5
|
-
use std::cell::RefCell;
|
|
6
|
-
use std::rc::Rc;
|
|
7
|
-
|
|
8
|
-
/// Type alias for a visitor handle (Rc-wrapped `RefCell` for interior mutability).
|
|
9
|
-
///
|
|
10
|
-
/// This allows visitors to be passed around and shared while still being mutable.
|
|
11
|
-
pub type VisitorHandle = Rc<RefCell<dyn super::traits::HtmlVisitor>>;
|
|
12
|
-
|
|
13
|
-
#[cfg(test)]
|
|
14
|
-
mod tests {
|
|
15
|
-
use super::super::traits::HtmlVisitor;
|
|
16
|
-
use super::super::types::{NodeContext, NodeType, VisitResult};
|
|
17
|
-
use std::collections::BTreeMap;
|
|
18
|
-
|
|
19
|
-
#[derive(Debug)]
|
|
20
|
-
struct TrackingVisitor {
|
|
21
|
-
element_count: usize,
|
|
22
|
-
text_count: usize,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
impl HtmlVisitor for TrackingVisitor {
|
|
26
|
-
fn visit_element_start(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
27
|
-
self.element_count += 1;
|
|
28
|
-
VisitResult::Continue
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
fn visit_text(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
32
|
-
self.text_count += 1;
|
|
33
|
-
VisitResult::Continue
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#[test]
|
|
38
|
-
fn test_visitor_handle_creation() {
|
|
39
|
-
let visitor = TrackingVisitor {
|
|
40
|
-
element_count: 0,
|
|
41
|
-
text_count: 0,
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
let handle = std::rc::Rc::new(std::cell::RefCell::new(visitor));
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
let mut v = handle.borrow_mut();
|
|
48
|
-
let ctx = NodeContext {
|
|
49
|
-
node_type: NodeType::Text,
|
|
50
|
-
tag_name: "p".to_string(),
|
|
51
|
-
attributes: BTreeMap::new(),
|
|
52
|
-
depth: 1,
|
|
53
|
-
index_in_parent: 0,
|
|
54
|
-
parent_tag: Some("div".to_string()),
|
|
55
|
-
is_inline: false,
|
|
56
|
-
};
|
|
57
|
-
v.visit_text(&ctx, "test");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let v = handle.borrow();
|
|
61
|
-
assert_eq!(v.text_count, 1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
//! Visitor pattern for HTML to Markdown conversion.
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides a comprehensive visitor trait that allows users to intervene
|
|
4
|
-
//! in the HTML→Markdown transformation logic at any point. Visitors can inspect,
|
|
5
|
-
//! modify, or replace the default conversion behavior for any HTML element type.
|
|
6
|
-
//!
|
|
7
|
-
//! # Design Philosophy
|
|
8
|
-
//!
|
|
9
|
-
//! - **Flexibility over performance**: Visitors prioritize giving users full control
|
|
10
|
-
//! - **Zero-cost when unused**: No performance impact if visitor feature is disabled
|
|
11
|
-
//! - **Comprehensive coverage**: All 60+ HTML element types have dedicated visitor methods
|
|
12
|
-
//! - **Pre/post hooks**: Both element entry and exit points are exposed
|
|
13
|
-
//!
|
|
14
|
-
//! # Example
|
|
15
|
-
//!
|
|
16
|
-
//! ```text
|
|
17
|
-
//! use html_to_markdown_rs::visitor::{HtmlVisitor, NodeContext, VisitResult};
|
|
18
|
-
//!
|
|
19
|
-
//! struct CustomVisitor;
|
|
20
|
-
//!
|
|
21
|
-
//! impl HtmlVisitor for CustomVisitor {
|
|
22
|
-
//! fn visit_link(&mut self, ctx: &NodeContext, href: &str, text: &str, title: Option<&str>) -> VisitResult {
|
|
23
|
-
//! // Convert all links to plain text with URL in parentheses
|
|
24
|
-
//! VisitResult::Custom(format!("{} ({})", text, href))
|
|
25
|
-
//! }
|
|
26
|
-
//!
|
|
27
|
-
//! fn visit_image(&mut self, ctx: &NodeContext, src: &str, alt: &str, title: Option<&str>) -> VisitResult {
|
|
28
|
-
//! // Skip all images
|
|
29
|
-
//! VisitResult::Skip
|
|
30
|
-
//! }
|
|
31
|
-
//! }
|
|
32
|
-
//! ```
|
|
33
|
-
|
|
34
|
-
mod default_impl;
|
|
35
|
-
mod traits;
|
|
36
|
-
mod types;
|
|
37
|
-
|
|
38
|
-
// Re-export all public items from submodules
|
|
39
|
-
pub use default_impl::VisitorHandle;
|
|
40
|
-
pub use traits::HtmlVisitor;
|
|
41
|
-
pub use types::{NodeContext, NodeType, VisitResult};
|
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
//! Visitor traits for HTML to Markdown conversion.
|
|
2
|
-
//!
|
|
3
|
-
//! This module contains the synchronous visitor trait.
|
|
4
|
-
|
|
5
|
-
use super::types::{NodeContext, VisitResult};
|
|
6
|
-
|
|
7
|
-
/// Visitor trait for HTML→Markdown conversion.
|
|
8
|
-
///
|
|
9
|
-
/// Implement this trait to customize the conversion behavior for any HTML element type.
|
|
10
|
-
/// All methods have default implementations that return `VisitResult::Continue`, allowing
|
|
11
|
-
/// selective override of only the elements you care about.
|
|
12
|
-
///
|
|
13
|
-
/// # Method Naming Convention
|
|
14
|
-
///
|
|
15
|
-
/// - `visit_*_start`: Called before entering an element (pre-order traversal)
|
|
16
|
-
/// - `visit_*_end`: Called after exiting an element (post-order traversal)
|
|
17
|
-
/// - `visit_*`: Called for specific element types (e.g., `visit_link`, `visit_image`)
|
|
18
|
-
///
|
|
19
|
-
/// # Execution Order
|
|
20
|
-
///
|
|
21
|
-
/// For a typical element like `<div><p>text</p></div>`:
|
|
22
|
-
/// 1. `visit_element_start` for `<div>`
|
|
23
|
-
/// 2. `visit_element_start` for `<p>`
|
|
24
|
-
/// 3. `visit_text` for "text"
|
|
25
|
-
/// 4. `visit_element_end` for `<p>`
|
|
26
|
-
/// 5. `visit_element_end` for `</div>`
|
|
27
|
-
///
|
|
28
|
-
/// # Performance Notes
|
|
29
|
-
///
|
|
30
|
-
/// - `visit_text` is the most frequently called method (~100+ times per document)
|
|
31
|
-
/// - Return `VisitResult::Continue` quickly for elements you don't need to customize
|
|
32
|
-
/// - Avoid heavy computation in visitor methods; consider caching if needed
|
|
33
|
-
pub trait HtmlVisitor: std::fmt::Debug {
|
|
34
|
-
/// Called before entering any element.
|
|
35
|
-
///
|
|
36
|
-
/// This is the first callback invoked for every HTML element, allowing
|
|
37
|
-
/// visitors to implement generic element handling before tag-specific logic.
|
|
38
|
-
fn visit_element_start(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
39
|
-
VisitResult::Continue
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/// Called after exiting any element.
|
|
43
|
-
///
|
|
44
|
-
/// Receives the default markdown output that would be generated.
|
|
45
|
-
/// Visitors can inspect or replace this output.
|
|
46
|
-
fn visit_element_end(&mut self, _ctx: &NodeContext, _output: &str) -> VisitResult {
|
|
47
|
-
VisitResult::Continue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/// Visit text nodes (most frequent callback - ~100+ per document).
|
|
51
|
-
///
|
|
52
|
-
/// # Arguments
|
|
53
|
-
/// - `ctx`: Node context (will have `node_type: NodeType::Text`)
|
|
54
|
-
/// - `text`: The raw text content (HTML entities already decoded)
|
|
55
|
-
fn visit_text(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
56
|
-
VisitResult::Continue
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/// Visit anchor links `<a href="...">`.
|
|
60
|
-
///
|
|
61
|
-
/// # Arguments
|
|
62
|
-
/// - `ctx`: Node context with link element metadata
|
|
63
|
-
/// - `href`: The link URL (from `href` attribute)
|
|
64
|
-
/// - `text`: The link text content (already converted to markdown)
|
|
65
|
-
/// - `title`: Optional title attribute
|
|
66
|
-
fn visit_link(&mut self, _ctx: &NodeContext, _href: &str, _text: &str, _title: Option<&str>) -> VisitResult {
|
|
67
|
-
VisitResult::Continue
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/// Visit images `<img src="...">`.
|
|
71
|
-
///
|
|
72
|
-
/// # Arguments
|
|
73
|
-
/// - `ctx`: Node context with image element metadata
|
|
74
|
-
/// - `src`: The image source URL
|
|
75
|
-
/// - `alt`: The alt text
|
|
76
|
-
/// - `title`: Optional title attribute
|
|
77
|
-
fn visit_image(&mut self, _ctx: &NodeContext, _src: &str, _alt: &str, _title: Option<&str>) -> VisitResult {
|
|
78
|
-
VisitResult::Continue
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/// Visit heading elements `<h1>` through `<h6>`.
|
|
82
|
-
///
|
|
83
|
-
/// # Arguments
|
|
84
|
-
/// - `ctx`: Node context with heading metadata
|
|
85
|
-
/// - `level`: Heading level (1-6)
|
|
86
|
-
/// - `text`: The heading text content
|
|
87
|
-
/// - `id`: Optional id attribute (for anchor links)
|
|
88
|
-
fn visit_heading(&mut self, _ctx: &NodeContext, _level: u32, _text: &str, _id: Option<&str>) -> VisitResult {
|
|
89
|
-
VisitResult::Continue
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/// Visit code blocks `<pre><code>`.
|
|
93
|
-
///
|
|
94
|
-
/// # Arguments
|
|
95
|
-
/// - `ctx`: Node context
|
|
96
|
-
/// - `lang`: Optional language specifier (from class attribute)
|
|
97
|
-
/// - `code`: The code content
|
|
98
|
-
fn visit_code_block(&mut self, _ctx: &NodeContext, _lang: Option<&str>, _code: &str) -> VisitResult {
|
|
99
|
-
VisitResult::Continue
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/// Visit inline code `<code>`.
|
|
103
|
-
///
|
|
104
|
-
/// # Arguments
|
|
105
|
-
/// - `ctx`: Node context
|
|
106
|
-
/// - `code`: The code content
|
|
107
|
-
fn visit_code_inline(&mut self, _ctx: &NodeContext, _code: &str) -> VisitResult {
|
|
108
|
-
VisitResult::Continue
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/// Visit list items `<li>`.
|
|
112
|
-
///
|
|
113
|
-
/// # Arguments
|
|
114
|
-
/// - `ctx`: Node context
|
|
115
|
-
/// - `ordered`: Whether this is an ordered list item
|
|
116
|
-
/// - `marker`: The list marker (e.g., "-", "1.", "a)")
|
|
117
|
-
/// - `text`: The list item content (already converted)
|
|
118
|
-
fn visit_list_item(&mut self, _ctx: &NodeContext, _ordered: bool, _marker: &str, _text: &str) -> VisitResult {
|
|
119
|
-
VisitResult::Continue
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/// Called before processing a list `<ul>` or `<ol>`.
|
|
123
|
-
fn visit_list_start(&mut self, _ctx: &NodeContext, _ordered: bool) -> VisitResult {
|
|
124
|
-
VisitResult::Continue
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/// Called after processing a list `</ul>` or `</ol>`.
|
|
128
|
-
fn visit_list_end(&mut self, _ctx: &NodeContext, _ordered: bool, _output: &str) -> VisitResult {
|
|
129
|
-
VisitResult::Continue
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/// Called before processing a table `<table>`.
|
|
133
|
-
fn visit_table_start(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
134
|
-
VisitResult::Continue
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/// Visit table rows `<tr>`.
|
|
138
|
-
///
|
|
139
|
-
/// # Arguments
|
|
140
|
-
/// - `ctx`: Node context
|
|
141
|
-
/// - `cells`: Cell contents (already converted to markdown)
|
|
142
|
-
/// - `is_header`: Whether this row is in `<thead>`
|
|
143
|
-
fn visit_table_row(&mut self, _ctx: &NodeContext, _cells: &[String], _is_header: bool) -> VisitResult {
|
|
144
|
-
VisitResult::Continue
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/// Called after processing a table `</table>`.
|
|
148
|
-
fn visit_table_end(&mut self, _ctx: &NodeContext, _output: &str) -> VisitResult {
|
|
149
|
-
VisitResult::Continue
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/// Visit blockquote elements `<blockquote>`.
|
|
153
|
-
///
|
|
154
|
-
/// # Arguments
|
|
155
|
-
/// - `ctx`: Node context
|
|
156
|
-
/// - `content`: The blockquote content (already converted)
|
|
157
|
-
/// - `depth`: Nesting depth (for nested blockquotes)
|
|
158
|
-
fn visit_blockquote(&mut self, _ctx: &NodeContext, _content: &str, _depth: usize) -> VisitResult {
|
|
159
|
-
VisitResult::Continue
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/// Visit strong/bold elements `<strong>`, `<b>`.
|
|
163
|
-
fn visit_strong(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
164
|
-
VisitResult::Continue
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/// Visit emphasis/italic elements `<em>`, `<i>`.
|
|
168
|
-
fn visit_emphasis(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
169
|
-
VisitResult::Continue
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/// Visit strikethrough elements `<s>`, `<del>`, `<strike>`.
|
|
173
|
-
fn visit_strikethrough(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
174
|
-
VisitResult::Continue
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/// Visit underline elements `<u>`, `<ins>`.
|
|
178
|
-
fn visit_underline(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
179
|
-
VisitResult::Continue
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/// Visit subscript elements `<sub>`.
|
|
183
|
-
fn visit_subscript(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
184
|
-
VisitResult::Continue
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/// Visit superscript elements `<sup>`.
|
|
188
|
-
fn visit_superscript(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
189
|
-
VisitResult::Continue
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/// Visit mark/highlight elements `<mark>`.
|
|
193
|
-
fn visit_mark(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
194
|
-
VisitResult::Continue
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/// Visit line break elements `<br>`.
|
|
198
|
-
fn visit_line_break(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
199
|
-
VisitResult::Continue
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/// Visit horizontal rule elements `<hr>`.
|
|
203
|
-
fn visit_horizontal_rule(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
204
|
-
VisitResult::Continue
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/// Visit custom elements (web components) or unknown tags.
|
|
208
|
-
///
|
|
209
|
-
/// # Arguments
|
|
210
|
-
/// - `ctx`: Node context
|
|
211
|
-
/// - `tag_name`: The custom element's tag name
|
|
212
|
-
/// - `html`: The raw HTML of this element
|
|
213
|
-
fn visit_custom_element(&mut self, _ctx: &NodeContext, _tag_name: &str, _html: &str) -> VisitResult {
|
|
214
|
-
VisitResult::Continue
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/// Visit definition list `<dl>`.
|
|
218
|
-
fn visit_definition_list_start(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
219
|
-
VisitResult::Continue
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/// Visit definition term `<dt>`.
|
|
223
|
-
fn visit_definition_term(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
224
|
-
VisitResult::Continue
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/// Visit definition description `<dd>`.
|
|
228
|
-
fn visit_definition_description(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
229
|
-
VisitResult::Continue
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/// Called after processing a definition list `</dl>`.
|
|
233
|
-
fn visit_definition_list_end(&mut self, _ctx: &NodeContext, _output: &str) -> VisitResult {
|
|
234
|
-
VisitResult::Continue
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/// Visit form elements `<form>`.
|
|
238
|
-
fn visit_form(&mut self, _ctx: &NodeContext, _action: Option<&str>, _method: Option<&str>) -> VisitResult {
|
|
239
|
-
VisitResult::Continue
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/// Visit input elements `<input>`.
|
|
243
|
-
fn visit_input(
|
|
244
|
-
&mut self,
|
|
245
|
-
_ctx: &NodeContext,
|
|
246
|
-
_input_type: &str,
|
|
247
|
-
_name: Option<&str>,
|
|
248
|
-
_value: Option<&str>,
|
|
249
|
-
) -> VisitResult {
|
|
250
|
-
VisitResult::Continue
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/// Visit button elements `<button>`.
|
|
254
|
-
fn visit_button(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
255
|
-
VisitResult::Continue
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/// Visit audio elements `<audio>`.
|
|
259
|
-
fn visit_audio(&mut self, _ctx: &NodeContext, _src: Option<&str>) -> VisitResult {
|
|
260
|
-
VisitResult::Continue
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/// Visit video elements `<video>`.
|
|
264
|
-
fn visit_video(&mut self, _ctx: &NodeContext, _src: Option<&str>) -> VisitResult {
|
|
265
|
-
VisitResult::Continue
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/// Visit iframe elements `<iframe>`.
|
|
269
|
-
fn visit_iframe(&mut self, _ctx: &NodeContext, _src: Option<&str>) -> VisitResult {
|
|
270
|
-
VisitResult::Continue
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/// Visit details elements `<details>`.
|
|
274
|
-
fn visit_details(&mut self, _ctx: &NodeContext, _open: bool) -> VisitResult {
|
|
275
|
-
VisitResult::Continue
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/// Visit summary elements `<summary>`.
|
|
279
|
-
fn visit_summary(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
280
|
-
VisitResult::Continue
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/// Visit figure elements `<figure>`.
|
|
284
|
-
fn visit_figure_start(&mut self, _ctx: &NodeContext) -> VisitResult {
|
|
285
|
-
VisitResult::Continue
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/// Visit figcaption elements `<figcaption>`.
|
|
289
|
-
fn visit_figcaption(&mut self, _ctx: &NodeContext, _text: &str) -> VisitResult {
|
|
290
|
-
VisitResult::Continue
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
/// Called after processing a figure `</figure>`.
|
|
294
|
-
fn visit_figure_end(&mut self, _ctx: &NodeContext, _output: &str) -> VisitResult {
|
|
295
|
-
VisitResult::Continue
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
#[cfg(test)]
|
|
300
|
-
mod tests {
|
|
301
|
-
use super::*;
|
|
302
|
-
use std::collections::BTreeMap;
|
|
303
|
-
|
|
304
|
-
#[derive(Debug)]
|
|
305
|
-
struct NoOpVisitor;
|
|
306
|
-
|
|
307
|
-
impl HtmlVisitor for NoOpVisitor {}
|
|
308
|
-
|
|
309
|
-
#[test]
|
|
310
|
-
fn test_default_visitor_implementation() {
|
|
311
|
-
let mut visitor = NoOpVisitor;
|
|
312
|
-
|
|
313
|
-
let ctx = NodeContext {
|
|
314
|
-
node_type: super::super::types::NodeType::Text,
|
|
315
|
-
tag_name: String::new(),
|
|
316
|
-
attributes: BTreeMap::new(),
|
|
317
|
-
depth: 0,
|
|
318
|
-
index_in_parent: 0,
|
|
319
|
-
parent_tag: None,
|
|
320
|
-
is_inline: true,
|
|
321
|
-
};
|
|
322
|
-
|
|
323
|
-
matches!(visitor.visit_element_start(&ctx), VisitResult::Continue);
|
|
324
|
-
matches!(visitor.visit_element_end(&ctx, "output"), VisitResult::Continue);
|
|
325
|
-
matches!(visitor.visit_text(&ctx, "text"), VisitResult::Continue);
|
|
326
|
-
matches!(visitor.visit_link(&ctx, "href", "text", None), VisitResult::Continue);
|
|
327
|
-
matches!(visitor.visit_image(&ctx, "src", "alt", None), VisitResult::Continue);
|
|
328
|
-
matches!(visitor.visit_heading(&ctx, 1, "text", None), VisitResult::Continue);
|
|
329
|
-
matches!(visitor.visit_code_block(&ctx, None, "code"), VisitResult::Continue);
|
|
330
|
-
matches!(visitor.visit_code_inline(&ctx, "code"), VisitResult::Continue);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
#[derive(Debug)]
|
|
334
|
-
struct CustomLinkVisitor;
|
|
335
|
-
|
|
336
|
-
impl HtmlVisitor for CustomLinkVisitor {
|
|
337
|
-
fn visit_link(&mut self, _ctx: &NodeContext, href: &str, text: &str, _title: Option<&str>) -> VisitResult {
|
|
338
|
-
VisitResult::Custom(format!("{} ({})", text, href))
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
fn visit_image(&mut self, _ctx: &NodeContext, _src: &str, _alt: &str, _title: Option<&str>) -> VisitResult {
|
|
342
|
-
VisitResult::Skip
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
#[test]
|
|
347
|
-
fn test_custom_visitor_implementation() {
|
|
348
|
-
let mut visitor = CustomLinkVisitor;
|
|
349
|
-
|
|
350
|
-
let ctx = NodeContext {
|
|
351
|
-
node_type: super::super::types::NodeType::Link,
|
|
352
|
-
tag_name: "a".to_string(),
|
|
353
|
-
attributes: BTreeMap::new(),
|
|
354
|
-
depth: 1,
|
|
355
|
-
index_in_parent: 0,
|
|
356
|
-
parent_tag: Some("p".to_string()),
|
|
357
|
-
is_inline: true,
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
let result = visitor.visit_link(&ctx, "https://example.com", "Example", None);
|
|
361
|
-
if let VisitResult::Custom(output) = result {
|
|
362
|
-
assert_eq!(output, "Example (https://example.com)");
|
|
363
|
-
} else {
|
|
364
|
-
panic!("Expected Custom result");
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
let img_result = visitor.visit_image(&ctx, "image.jpg", "Alt text", None);
|
|
368
|
-
matches!(img_result, VisitResult::Skip);
|
|
369
|
-
}
|
|
370
|
-
}
|