html-to-markdown 3.4.0 → 3.6.0.pre.rc.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,201 +0,0 @@
|
|
|
1
|
-
//! HTML preprocessing and validation helpers.
|
|
2
|
-
//!
|
|
3
|
-
//! This module contains helper functions for preprocessing HTML before conversion,
|
|
4
|
-
//! including validation and normalization checks.
|
|
5
|
-
|
|
6
|
-
use crate::converter::dom_context::DomContext;
|
|
7
|
-
use crate::converter::main_helpers::is_inline_element;
|
|
8
|
-
use crate::converter::utility::attributes::{attribute_matches_any, element_has_navigation_hint};
|
|
9
|
-
use crate::converter::utility::content::normalized_tag_name;
|
|
10
|
-
use crate::options::ConversionOptions;
|
|
11
|
-
|
|
12
|
-
/// Check if an inline ancestor element is allowed to contain block-level elements.
|
|
13
|
-
pub fn inline_ancestor_allows_block(tag_name: &str) -> bool {
|
|
14
|
-
matches!(tag_name, "a" | "ins" | "del")
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/// Detect block elements that were incorrectly nested under inline ancestors.
|
|
18
|
-
///
|
|
19
|
-
/// Excludes elements inside `<pre>` or `<code>` blocks, as they have special
|
|
20
|
-
/// whitespace preservation rules and should not be repaired.
|
|
21
|
-
///
|
|
22
|
-
/// Also detects table structural elements (`td`, `tr`, `th`) nested under `<p>` —
|
|
23
|
-
/// a structural impossibility in valid HTML that signals the `tl` parser absorbed
|
|
24
|
-
/// a table into a paragraph because of an unclosed `<p>` (common in Word/Outlook
|
|
25
|
-
/// HTML such as `<p class='MsoNormal'>` cells). Issue #336.
|
|
26
|
-
pub fn has_inline_block_misnest(dom_ctx: &DomContext, parser: &tl::Parser) -> bool {
|
|
27
|
-
for handle in dom_ctx.node_map.iter().flatten() {
|
|
28
|
-
if let Some(tl::Node::Tag(_tag)) = handle.get(parser) {
|
|
29
|
-
let node_id = handle.get_inner();
|
|
30
|
-
let Some(info) = dom_ctx.tag_info(node_id, parser) else {
|
|
31
|
-
continue;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Table elements under <p>: tl misparsed an unclosed <p> in <td>.
|
|
35
|
-
if matches!(info.name.as_str(), "td" | "tr" | "th") && has_p_ancestor(dom_ctx, parser, node_id) {
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if !info.is_block {
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Check if this block element or any ancestor is pre/code
|
|
44
|
-
let mut check_parent = Some(node_id);
|
|
45
|
-
let mut inside_preformatted = false;
|
|
46
|
-
while let Some(check_id) = check_parent {
|
|
47
|
-
if let Some(info) = dom_ctx.tag_info(check_id, parser) {
|
|
48
|
-
if matches!(info.name.as_str(), "pre" | "code") {
|
|
49
|
-
inside_preformatted = true;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
check_parent = dom_ctx.parent_of(check_id);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Skip misnesting check for elements inside pre/code blocks
|
|
57
|
-
if inside_preformatted {
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
let mut current = dom_ctx.parent_of(node_id);
|
|
62
|
-
while let Some(parent_id) = current {
|
|
63
|
-
if let Some(parent_info) = dom_ctx.tag_info(parent_id, parser) {
|
|
64
|
-
if is_inline_element(&parent_info.name) && !inline_ancestor_allows_block(&parent_info.name) {
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
} else if let Some(parent_handle) = dom_ctx.node_handle(parent_id) {
|
|
68
|
-
if let Some(tl::Node::Tag(parent_tag)) = parent_handle.get(parser) {
|
|
69
|
-
let parent_name = normalized_tag_name(parent_tag.name().as_utf8_str());
|
|
70
|
-
if is_inline_element(&parent_name) && !inline_ancestor_allows_block(&parent_name) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
current = dom_ctx.parent_of(parent_id);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
false
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/// Walk ancestors of `node_id` looking for a `<p>` element.
|
|
84
|
-
///
|
|
85
|
-
/// Stops ascending once it leaves the table hierarchy (`table`/`body`/`html`)
|
|
86
|
-
/// to avoid false positives where a `<p>` legitimately wraps a `<table>`.
|
|
87
|
-
fn has_p_ancestor(dom_ctx: &DomContext, parser: &tl::Parser, node_id: u32) -> bool {
|
|
88
|
-
let mut current = dom_ctx.parent_of(node_id);
|
|
89
|
-
while let Some(parent_id) = current {
|
|
90
|
-
if let Some(parent_info) = dom_ctx.tag_info(parent_id, parser) {
|
|
91
|
-
if parent_info.name == "p" {
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
if matches!(parent_info.name.as_str(), "table" | "body" | "html") {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
current = dom_ctx.parent_of(parent_id);
|
|
99
|
-
}
|
|
100
|
-
false
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/// Determine if a node should be dropped during preprocessing.
|
|
104
|
-
///
|
|
105
|
-
/// Behavior depends on the [`PreprocessingPreset`]:
|
|
106
|
-
///
|
|
107
|
-
/// - **Minimal**: Only scripts/styles are stripped (handled elsewhere). This function
|
|
108
|
-
/// drops nothing — all structural elements are preserved.
|
|
109
|
-
/// - **Standard** (default): Drops `<nav>` unconditionally. Drops `<header>`, `<footer>`,
|
|
110
|
-
/// and `<aside>` only when they have navigation hints (class/role/aria attributes
|
|
111
|
-
/// indicating site chrome). Drops `<form>` when `remove_forms` is enabled.
|
|
112
|
-
/// - **Aggressive**: All of Standard, plus: drops `<footer>`, `<aside>`, `<noscript>`
|
|
113
|
-
/// unconditionally. Drops ANY element with navigation hints in class/id/role
|
|
114
|
-
/// (e.g. `<div class="sidebar">`). Drops elements with noise-related classes/roles.
|
|
115
|
-
pub fn should_drop_for_preprocessing(tag_name: &str, tag: &tl::HTMLTag, options: &ConversionOptions) -> bool {
|
|
116
|
-
use crate::options::PreprocessingPreset;
|
|
117
|
-
|
|
118
|
-
if !options.preprocessing.enabled {
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
let preset = options.preprocessing.preset;
|
|
123
|
-
|
|
124
|
-
// Minimal preset: drop nothing here (scripts/styles handled in earlier pipeline stage).
|
|
125
|
-
if preset == PreprocessingPreset::Minimal {
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Form removal — applies to both Standard and Aggressive when enabled.
|
|
130
|
-
if options.preprocessing.remove_forms && tag_name == "form" {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let is_aggressive = preset == PreprocessingPreset::Aggressive;
|
|
135
|
-
|
|
136
|
-
// Aggressive: drop <noscript> — its content is fallback for no-JS browsers.
|
|
137
|
-
if is_aggressive && tag_name == "noscript" {
|
|
138
|
-
return true;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Navigation removal — only when the flag is enabled.
|
|
142
|
-
if !options.preprocessing.remove_navigation {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
let has_nav_hint = element_has_navigation_hint(tag);
|
|
147
|
-
|
|
148
|
-
// <nav> is always navigation — drop in both Standard and Aggressive.
|
|
149
|
-
if tag_name == "nav" {
|
|
150
|
-
return true;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if tag_name == "header" {
|
|
154
|
-
// Drop <header> only with navigation hints (e.g. class="site-header",
|
|
155
|
-
// role="navigation"). A plain <header> often wraps article titles like
|
|
156
|
-
// <header><h1>Title</h1></header> — dropping it loses content.
|
|
157
|
-
return has_nav_hint;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if tag_name == "footer" || tag_name == "aside" {
|
|
161
|
-
// Standard: drop only with navigation hints.
|
|
162
|
-
// Aggressive: drop unconditionally.
|
|
163
|
-
return is_aggressive || has_nav_hint;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Aggressive: drop ANY element that has navigation hints in class/id/role.
|
|
167
|
-
// This catches <div class="sidebar">, <div class="menu">, <section class="navigation">,
|
|
168
|
-
// and similar non-semantic navigation containers.
|
|
169
|
-
if is_aggressive && has_nav_hint {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Aggressive: drop elements with noise-related roles.
|
|
174
|
-
if is_aggressive {
|
|
175
|
-
if element_has_noise_hint(tag) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
false
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/// Check if an element has noise-related hints (ads, cookie banners, social sharing).
|
|
184
|
-
fn element_has_noise_hint(tag: &tl::HTMLTag) -> bool {
|
|
185
|
-
const NOISE_KEYWORDS: &[&str] = &[
|
|
186
|
-
"cookie",
|
|
187
|
-
"consent",
|
|
188
|
-
"gdpr",
|
|
189
|
-
"banner",
|
|
190
|
-
"advertisement",
|
|
191
|
-
"ad-container",
|
|
192
|
-
"advert",
|
|
193
|
-
"social-share",
|
|
194
|
-
"share-buttons",
|
|
195
|
-
"popup",
|
|
196
|
-
"modal-overlay",
|
|
197
|
-
"newsletter-signup",
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
attribute_matches_any(tag, "class", NOISE_KEYWORDS) || attribute_matches_any(tag, "id", NOISE_KEYWORDS)
|
|
201
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
//! Collector for reference-style link definitions.
|
|
2
|
-
|
|
3
|
-
use std::cell::RefCell;
|
|
4
|
-
use std::collections::HashMap;
|
|
5
|
-
use std::rc::Rc;
|
|
6
|
-
|
|
7
|
-
/// Shared handle for passing the collector through the conversion context.
|
|
8
|
-
pub type ReferenceCollectorHandle = Rc<RefCell<ReferenceCollector>>;
|
|
9
|
-
|
|
10
|
-
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
|
|
11
|
-
struct ReferenceKey {
|
|
12
|
-
url: String,
|
|
13
|
-
title: Option<String>,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/// Collects link/image references during conversion and produces a reference
|
|
17
|
-
/// definitions section at the end of the document.
|
|
18
|
-
#[derive(Debug, Default)]
|
|
19
|
-
pub struct ReferenceCollector {
|
|
20
|
-
map: HashMap<ReferenceKey, usize>,
|
|
21
|
-
entries: Vec<(usize, String, Option<String>)>,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
impl ReferenceCollector {
|
|
25
|
-
/// Create a new, empty reference collector.
|
|
26
|
-
pub fn new() -> Self {
|
|
27
|
-
Self::default()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/// Register a URL (and optional title) and return its 1-based reference number.
|
|
31
|
-
///
|
|
32
|
-
/// If the same URL+title pair was already registered, the existing number is returned.
|
|
33
|
-
pub fn get_or_insert(&mut self, url: &str, title: Option<&str>) -> usize {
|
|
34
|
-
let key = ReferenceKey {
|
|
35
|
-
url: url.to_string(),
|
|
36
|
-
title: title.map(String::from),
|
|
37
|
-
};
|
|
38
|
-
if let Some(&num) = self.map.get(&key) {
|
|
39
|
-
return num;
|
|
40
|
-
}
|
|
41
|
-
let num = self.entries.len() + 1;
|
|
42
|
-
self.map.insert(key, num);
|
|
43
|
-
self.entries.push((num, url.to_string(), title.map(String::from)));
|
|
44
|
-
num
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/// Produce the reference definitions section.
|
|
48
|
-
///
|
|
49
|
-
/// Returns an empty string when no references were collected.
|
|
50
|
-
pub fn finish(&self) -> String {
|
|
51
|
-
if self.entries.is_empty() {
|
|
52
|
-
return String::new();
|
|
53
|
-
}
|
|
54
|
-
let mut out = String::new();
|
|
55
|
-
for (num, url, title) in &self.entries {
|
|
56
|
-
out.push('[');
|
|
57
|
-
out.push_str(&num.to_string());
|
|
58
|
-
out.push_str("]: ");
|
|
59
|
-
out.push_str(url);
|
|
60
|
-
if let Some(t) = title {
|
|
61
|
-
out.push_str(" \"");
|
|
62
|
-
out.push_str(&t.replace('"', "\\\""));
|
|
63
|
-
out.push('"');
|
|
64
|
-
}
|
|
65
|
-
out.push('\n');
|
|
66
|
-
}
|
|
67
|
-
out
|
|
68
|
-
}
|
|
69
|
-
}
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
//! Handlers for semantic inline elements with attributes.
|
|
2
|
-
//!
|
|
3
|
-
//! Processes semantic inline elements that often carry semantic meaning through attributes:
|
|
4
|
-
//! - `<cite>` - Citation/reference to a source
|
|
5
|
-
//! - `<q>` - Inline quotation
|
|
6
|
-
//! - `<abbr>` - Abbreviation with optional title explanation
|
|
7
|
-
//! - `<dfn>` - Definition of a term
|
|
8
|
-
//! - `<time>` - Machine-readable date/time
|
|
9
|
-
//! - `<data>` - Machine-readable data value
|
|
10
|
-
//!
|
|
11
|
-
//! These elements carry semantic meaning in HTML5 but often have minimal
|
|
12
|
-
//! visual distinction in rendered Markdown. Some are formatted with emphasis
|
|
13
|
-
//! or have their attributes included in the output.
|
|
14
|
-
|
|
15
|
-
// Note: Context and DomContext are defined in converter.rs
|
|
16
|
-
// walk_node is also defined there and must be called via the parent module
|
|
17
|
-
|
|
18
|
-
use crate::converter::utility::content::chomp_inline;
|
|
19
|
-
|
|
20
|
-
/// Handles the `<dfn>` element.
|
|
21
|
-
///
|
|
22
|
-
/// A dfn element marks a term that is being defined. The content represents
|
|
23
|
-
/// the term, and its definition would typically appear in surrounding context.
|
|
24
|
-
/// It is rendered as emphasized (italic) text.
|
|
25
|
-
///
|
|
26
|
-
/// # Behavior
|
|
27
|
-
///
|
|
28
|
-
/// - Content is collected from children
|
|
29
|
-
/// - Non-empty content is wrapped with the configured emphasis symbol (default: `*`)
|
|
30
|
-
/// - Inline suffix handling is applied (e.g., footnote references)
|
|
31
|
-
pub fn handle_dfn(
|
|
32
|
-
_tag_name: &str,
|
|
33
|
-
node_handle: &tl::NodeHandle,
|
|
34
|
-
parser: &tl::Parser,
|
|
35
|
-
output: &mut String,
|
|
36
|
-
options: &crate::options::ConversionOptions,
|
|
37
|
-
ctx: &super::Context,
|
|
38
|
-
depth: usize,
|
|
39
|
-
dom_ctx: &super::DomContext,
|
|
40
|
-
) {
|
|
41
|
-
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
|
|
42
|
-
let mut content = String::with_capacity(32);
|
|
43
|
-
let children = tag.children();
|
|
44
|
-
{
|
|
45
|
-
for child_handle in children.top().iter() {
|
|
46
|
-
super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let (prefix, suffix, trimmed) = chomp_inline(&content);
|
|
51
|
-
if !trimmed.is_empty() {
|
|
52
|
-
output.push_str(prefix);
|
|
53
|
-
output.push(options.strong_em_symbol);
|
|
54
|
-
output.push_str(trimmed);
|
|
55
|
-
output.push(options.strong_em_symbol);
|
|
56
|
-
append_inline_suffix(output, suffix, !trimmed.is_empty(), node_handle, parser, dom_ctx);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/// Handles the `<abbr>` element.
|
|
62
|
-
///
|
|
63
|
-
/// An abbr element marks an abbreviation or acronym. The `title` attribute
|
|
64
|
-
/// provides the expansion of the abbreviation, which is appended in parentheses
|
|
65
|
-
/// if present.
|
|
66
|
-
///
|
|
67
|
-
/// # Behavior
|
|
68
|
-
///
|
|
69
|
-
/// - Content is collected from children
|
|
70
|
-
/// - Non-empty content is output as-is
|
|
71
|
-
/// - If `title` attribute exists, it is appended in parentheses: `abbr (title)`
|
|
72
|
-
///
|
|
73
|
-
/// # Example
|
|
74
|
-
///
|
|
75
|
-
/// ```html
|
|
76
|
-
/// <abbr title="HyperText Markup Language">HTML</abbr>
|
|
77
|
-
/// ```
|
|
78
|
-
///
|
|
79
|
-
/// Produces: `HTML (HyperText Markup Language)`
|
|
80
|
-
pub fn handle_abbr(
|
|
81
|
-
_tag_name: &str,
|
|
82
|
-
node_handle: &tl::NodeHandle,
|
|
83
|
-
parser: &tl::Parser,
|
|
84
|
-
output: &mut String,
|
|
85
|
-
options: &crate::options::ConversionOptions,
|
|
86
|
-
ctx: &super::Context,
|
|
87
|
-
depth: usize,
|
|
88
|
-
dom_ctx: &super::DomContext,
|
|
89
|
-
) {
|
|
90
|
-
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
|
|
91
|
-
let mut content = String::with_capacity(32);
|
|
92
|
-
let children = tag.children();
|
|
93
|
-
{
|
|
94
|
-
for child_handle in children.top().iter() {
|
|
95
|
-
super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let trimmed = content.trim();
|
|
100
|
-
if !trimmed.is_empty() {
|
|
101
|
-
output.push_str(trimmed);
|
|
102
|
-
|
|
103
|
-
// Append title attribute if present
|
|
104
|
-
if let Some(title) = tag.attributes().get("title").flatten().map(|v| v.as_utf8_str()) {
|
|
105
|
-
let trimmed_title = title.trim();
|
|
106
|
-
if !trimmed_title.is_empty() {
|
|
107
|
-
output.push_str(" (");
|
|
108
|
-
output.push_str(trimmed_title);
|
|
109
|
-
output.push(')');
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/// Handles the `<time>` and `<data>` elements.
|
|
117
|
-
///
|
|
118
|
-
/// Time and data elements contain machine-readable content in their attributes
|
|
119
|
-
/// and human-readable content in their text. For Markdown purposes, we output
|
|
120
|
-
/// only the human-readable text content, as Markdown doesn't have a way to
|
|
121
|
-
/// preserve machine-readable metadata.
|
|
122
|
-
///
|
|
123
|
-
/// # Behavior
|
|
124
|
-
///
|
|
125
|
-
/// - Content is extracted from children and output as-is
|
|
126
|
-
/// - Attributes (datetime, value) are not rendered in Markdown output
|
|
127
|
-
pub fn handle_time_data(
|
|
128
|
-
_tag_name: &str,
|
|
129
|
-
node_handle: &tl::NodeHandle,
|
|
130
|
-
parser: &tl::Parser,
|
|
131
|
-
output: &mut String,
|
|
132
|
-
options: &crate::options::ConversionOptions,
|
|
133
|
-
ctx: &super::Context,
|
|
134
|
-
depth: usize,
|
|
135
|
-
dom_ctx: &super::DomContext,
|
|
136
|
-
) {
|
|
137
|
-
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
|
|
138
|
-
let children = tag.children();
|
|
139
|
-
{
|
|
140
|
-
for child_handle in children.top().iter() {
|
|
141
|
-
super::walk_node(child_handle, parser, output, options, ctx, depth + 1, dom_ctx);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/// Handles the `<cite>` element.
|
|
148
|
-
///
|
|
149
|
-
/// A cite element marks the title of a cited work (book, article, website, etc.).
|
|
150
|
-
/// It is rendered as emphasized (italic) text in block mode, or as plain text in inline mode.
|
|
151
|
-
///
|
|
152
|
-
/// # Behavior
|
|
153
|
-
///
|
|
154
|
-
/// - **Block mode**: Content is wrapped with emphasis markers (default: `*`)
|
|
155
|
-
/// - **Inline mode**: Content is output as-is without formatting
|
|
156
|
-
pub fn handle_cite(
|
|
157
|
-
_tag_name: &str,
|
|
158
|
-
node_handle: &tl::NodeHandle,
|
|
159
|
-
parser: &tl::Parser,
|
|
160
|
-
output: &mut String,
|
|
161
|
-
options: &crate::options::ConversionOptions,
|
|
162
|
-
ctx: &super::Context,
|
|
163
|
-
depth: usize,
|
|
164
|
-
dom_ctx: &super::DomContext,
|
|
165
|
-
) {
|
|
166
|
-
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
|
|
167
|
-
let mut content = String::with_capacity(32);
|
|
168
|
-
let children = tag.children();
|
|
169
|
-
{
|
|
170
|
-
for child_handle in children.top().iter() {
|
|
171
|
-
super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let trimmed = content.trim();
|
|
176
|
-
if !trimmed.is_empty() {
|
|
177
|
-
if ctx.convert_as_inline {
|
|
178
|
-
output.push_str(trimmed);
|
|
179
|
-
} else {
|
|
180
|
-
output.push('*');
|
|
181
|
-
output.push_str(trimmed);
|
|
182
|
-
output.push('*');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/// Handles the `<q>` element.
|
|
189
|
-
///
|
|
190
|
-
/// A q element marks an inline quotation. In Markdown, it is rendered as
|
|
191
|
-
/// quoted text enclosed in double quotes. Backslashes and quotes within
|
|
192
|
-
/// the content are escaped.
|
|
193
|
-
///
|
|
194
|
-
/// # Behavior
|
|
195
|
-
///
|
|
196
|
-
/// - **Block mode**: Content is wrapped in escaped double quotes: `"content"`
|
|
197
|
-
/// - **Inline mode**: Content is output as-is without quotes
|
|
198
|
-
///
|
|
199
|
-
/// # Escaping
|
|
200
|
-
///
|
|
201
|
-
/// Internal backslashes and double quotes are escaped:
|
|
202
|
-
/// - `\` → `\\`
|
|
203
|
-
/// - `"` → `\"`
|
|
204
|
-
pub fn handle_q(
|
|
205
|
-
_tag_name: &str,
|
|
206
|
-
node_handle: &tl::NodeHandle,
|
|
207
|
-
parser: &tl::Parser,
|
|
208
|
-
output: &mut String,
|
|
209
|
-
options: &crate::options::ConversionOptions,
|
|
210
|
-
ctx: &super::Context,
|
|
211
|
-
depth: usize,
|
|
212
|
-
dom_ctx: &super::DomContext,
|
|
213
|
-
) {
|
|
214
|
-
if let Some(tl::Node::Tag(tag)) = node_handle.get(parser) {
|
|
215
|
-
let mut content = String::with_capacity(32);
|
|
216
|
-
let children = tag.children();
|
|
217
|
-
{
|
|
218
|
-
for child_handle in children.top().iter() {
|
|
219
|
-
super::walk_node(child_handle, parser, &mut content, options, ctx, depth + 1, dom_ctx);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
let trimmed = content.trim();
|
|
224
|
-
if !trimmed.is_empty() {
|
|
225
|
-
output.push('"');
|
|
226
|
-
output.push_str(trimmed);
|
|
227
|
-
output.push('"');
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/// Dispatcher for semantic inline attribute elements.
|
|
233
|
-
///
|
|
234
|
-
/// Routes `<cite>`, `<q>`, `<abbr>`, `<dfn>`, `<time>`, and `<data>` elements
|
|
235
|
-
/// to their respective handlers.
|
|
236
|
-
pub fn handle(
|
|
237
|
-
tag_name: &str,
|
|
238
|
-
node_handle: &tl::NodeHandle,
|
|
239
|
-
parser: &tl::Parser,
|
|
240
|
-
output: &mut String,
|
|
241
|
-
options: &crate::options::ConversionOptions,
|
|
242
|
-
ctx: &super::Context,
|
|
243
|
-
depth: usize,
|
|
244
|
-
dom_ctx: &super::DomContext,
|
|
245
|
-
) {
|
|
246
|
-
match tag_name {
|
|
247
|
-
"dfn" => handle_dfn(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
|
|
248
|
-
"abbr" => handle_abbr(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
|
|
249
|
-
"time" | "data" => handle_time_data(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
|
|
250
|
-
"cite" => handle_cite(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
|
|
251
|
-
"q" => handle_q(tag_name, node_handle, parser, output, options, ctx, depth, dom_ctx),
|
|
252
|
-
_ => {}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/// Appends inline suffix to the output.
|
|
257
|
-
///
|
|
258
|
-
/// This is a placeholder for integrating with other inline formatting systems
|
|
259
|
-
/// (e.g., footnote references). For now, it simply outputs the suffix.
|
|
260
|
-
fn append_inline_suffix(
|
|
261
|
-
output: &mut String,
|
|
262
|
-
suffix: &str,
|
|
263
|
-
_is_nonempty: bool,
|
|
264
|
-
_node_handle: &tl::NodeHandle,
|
|
265
|
-
_parser: &tl::Parser,
|
|
266
|
-
_dom_ctx: &super::DomContext,
|
|
267
|
-
) {
|
|
268
|
-
output.push_str(suffix);
|
|
269
|
-
}
|