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,140 +0,0 @@
|
|
|
1
|
-
//! Handler for paragraph elements (p, div).
|
|
2
|
-
//!
|
|
3
|
-
//! Converts HTML paragraph tags to Markdown paragraphs with proper spacing
|
|
4
|
-
//! and support for:
|
|
5
|
-
//! - Continuation handling in tables and lists
|
|
6
|
-
//! - Proper blank line spacing
|
|
7
|
-
//! - Empty element filtering
|
|
8
|
-
//! - Visitor callbacks for custom paragraph processing
|
|
9
|
-
|
|
10
|
-
use crate::options::ConversionOptions;
|
|
11
|
-
use tl::{NodeHandle, Parser};
|
|
12
|
-
|
|
13
|
-
// Type aliases for Context and DomContext to avoid circular imports
|
|
14
|
-
type Context = crate::converter::Context;
|
|
15
|
-
type DomContext = crate::converter::DomContext;
|
|
16
|
-
|
|
17
|
-
/// Handle paragraph elements (p, div).
|
|
18
|
-
///
|
|
19
|
-
/// Processes children with proper context, manages spacing,
|
|
20
|
-
/// and handles special cases for table cells and list items.
|
|
21
|
-
pub fn handle(
|
|
22
|
-
node_handle: &NodeHandle,
|
|
23
|
-
parser: &Parser,
|
|
24
|
-
output: &mut String,
|
|
25
|
-
options: &ConversionOptions,
|
|
26
|
-
ctx: &Context,
|
|
27
|
-
depth: usize,
|
|
28
|
-
dom_ctx: &DomContext,
|
|
29
|
-
) {
|
|
30
|
-
use crate::converter::walk_node;
|
|
31
|
-
|
|
32
|
-
let content_start_pos = output.len();
|
|
33
|
-
|
|
34
|
-
let is_table_continuation =
|
|
35
|
-
ctx.in_table_cell && !output.is_empty() && !output.ends_with('|') && !output.ends_with("<br>");
|
|
36
|
-
|
|
37
|
-
let is_list_continuation = ctx.in_list_item
|
|
38
|
-
&& !output.is_empty()
|
|
39
|
-
&& !output.ends_with("* ")
|
|
40
|
-
&& !output.ends_with("- ")
|
|
41
|
-
&& !output.ends_with(". ");
|
|
42
|
-
|
|
43
|
-
let after_code_block = output.ends_with("```\n");
|
|
44
|
-
let needs_leading_sep = !ctx.in_table_cell
|
|
45
|
-
&& !ctx.in_list_item
|
|
46
|
-
&& !ctx.convert_as_inline
|
|
47
|
-
&& ctx.blockquote_depth == 0
|
|
48
|
-
&& !output.is_empty()
|
|
49
|
-
&& !output.ends_with("\n\n")
|
|
50
|
-
&& !after_code_block;
|
|
51
|
-
|
|
52
|
-
if is_table_continuation {
|
|
53
|
-
crate::converter::trim_trailing_whitespace(output);
|
|
54
|
-
output.push_str("<br>");
|
|
55
|
-
} else if is_list_continuation {
|
|
56
|
-
add_list_continuation_indent(output, ctx.list_depth, true, options);
|
|
57
|
-
} else if needs_leading_sep {
|
|
58
|
-
crate::converter::trim_trailing_whitespace(output);
|
|
59
|
-
output.push_str("\n\n");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let p_ctx = Context {
|
|
63
|
-
in_paragraph: true,
|
|
64
|
-
block_content_start: output.len(),
|
|
65
|
-
..ctx.clone()
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
if let Some(node) = node_handle.get(parser) {
|
|
69
|
-
if let tl::Node::Tag(tag) = node {
|
|
70
|
-
let children = tag.children();
|
|
71
|
-
let child_handles: Vec<_> = children.top().iter().collect();
|
|
72
|
-
|
|
73
|
-
for (i, child_handle) in child_handles.iter().enumerate() {
|
|
74
|
-
if let Some(node) = child_handle.get(parser) {
|
|
75
|
-
if let tl::Node::Raw(bytes) = node {
|
|
76
|
-
let text = bytes.as_utf8_str();
|
|
77
|
-
if text.trim().is_empty() && i > 0 && i < child_handles.len() - 1 {
|
|
78
|
-
let prev = &child_handles[i - 1];
|
|
79
|
-
let next = &child_handles[i + 1];
|
|
80
|
-
if is_empty_inline_element(prev, parser, dom_ctx)
|
|
81
|
-
&& is_empty_inline_element(next, parser, dom_ctx)
|
|
82
|
-
{
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
walk_node(child_handle, parser, output, options, &p_ctx, depth + 1, dom_ctx);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let has_content = output.len() > content_start_pos;
|
|
95
|
-
|
|
96
|
-
if has_content && !ctx.convert_as_inline && !ctx.in_table_cell {
|
|
97
|
-
output.push_str("\n\n");
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Notify the structure collector if present and we produced non-empty top-level paragraph content.
|
|
101
|
-
if has_content && !ctx.in_table_cell && !ctx.in_list_item && !ctx.convert_as_inline {
|
|
102
|
-
if let Some(ref sc) = ctx.structure_collector {
|
|
103
|
-
let text = output[content_start_pos..].trim().to_string();
|
|
104
|
-
if !text.is_empty() {
|
|
105
|
-
sc.borrow_mut().push_paragraph(&text);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/// Add continuation indentation for list items.
|
|
112
|
-
fn add_list_continuation_indent(
|
|
113
|
-
output: &mut String,
|
|
114
|
-
list_depth: usize,
|
|
115
|
-
needs_space: bool,
|
|
116
|
-
_options: &ConversionOptions,
|
|
117
|
-
) {
|
|
118
|
-
if needs_space && !output.ends_with(' ') && !output.ends_with('\n') {
|
|
119
|
-
output.push(' ');
|
|
120
|
-
}
|
|
121
|
-
for _ in 0..(4 * list_depth) {
|
|
122
|
-
output.push(' ');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/// Check if an element is empty (has no text content).
|
|
127
|
-
fn is_empty_inline_element(node_handle: &NodeHandle, parser: &Parser, _dom_ctx: &DomContext) -> bool {
|
|
128
|
-
if let Some(node) = node_handle.get(parser) {
|
|
129
|
-
match node {
|
|
130
|
-
tl::Node::Tag(tag) => {
|
|
131
|
-
let tag_name = tag.name().as_utf8_str();
|
|
132
|
-
// Elements that are always empty or only contain attributes
|
|
133
|
-
matches!(tag_name.as_ref(), "br" | "hr" | "img" | "input" | "meta" | "link")
|
|
134
|
-
}
|
|
135
|
-
_ => false,
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
false
|
|
139
|
-
}
|
|
140
|
-
}
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
//! Handler for preformatted code elements (pre, code).
|
|
2
|
-
//!
|
|
3
|
-
//! Converts HTML preformatted and code tags to Markdown code blocks with support for:
|
|
4
|
-
//! - Language detection from `class` attributes (language-*, lang-*)
|
|
5
|
-
//! - Multiple code block styles (indented, backticks, tildes)
|
|
6
|
-
//! - Code dedentation and whitespace normalization
|
|
7
|
-
//! - Inline code formatting with backtick management
|
|
8
|
-
//! - Visitor callbacks for custom code processing
|
|
9
|
-
|
|
10
|
-
#[cfg(feature = "visitor")]
|
|
11
|
-
use crate::converter::utility::content::collect_tag_attributes;
|
|
12
|
-
use crate::options::{CodeBlockStyle, ConversionOptions, WhitespaceMode};
|
|
13
|
-
#[allow(unused_imports)]
|
|
14
|
-
use std::collections::BTreeMap;
|
|
15
|
-
use tl::{NodeHandle, Parser};
|
|
16
|
-
|
|
17
|
-
// Type aliases for Context and DomContext to avoid circular imports
|
|
18
|
-
type Context = crate::converter::Context;
|
|
19
|
-
type DomContext = crate::converter::DomContext;
|
|
20
|
-
|
|
21
|
-
/// Handle preformatted code blocks (pre element).
|
|
22
|
-
pub fn handle_pre(
|
|
23
|
-
node_handle: &NodeHandle,
|
|
24
|
-
parser: &Parser,
|
|
25
|
-
output: &mut String,
|
|
26
|
-
options: &ConversionOptions,
|
|
27
|
-
ctx: &Context,
|
|
28
|
-
depth: usize,
|
|
29
|
-
dom_ctx: &DomContext,
|
|
30
|
-
) {
|
|
31
|
-
use crate::converter::walk_node;
|
|
32
|
-
|
|
33
|
-
let code_ctx = Context {
|
|
34
|
-
in_code: true,
|
|
35
|
-
..ctx.clone()
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
let language = extract_language_from_pre(node_handle, parser);
|
|
39
|
-
|
|
40
|
-
let mut content = String::with_capacity(256);
|
|
41
|
-
if let Some(node) = node_handle.get(parser) {
|
|
42
|
-
if let tl::Node::Tag(tag) = node {
|
|
43
|
-
let children = tag.children();
|
|
44
|
-
for child_handle in children.top().iter() {
|
|
45
|
-
walk_node(
|
|
46
|
-
child_handle,
|
|
47
|
-
parser,
|
|
48
|
-
&mut content,
|
|
49
|
-
options,
|
|
50
|
-
&code_ctx,
|
|
51
|
-
depth + 1,
|
|
52
|
-
dom_ctx,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if !content.is_empty() {
|
|
59
|
-
let leading_newlines = content.chars().take_while(|&c| c == '\n').count();
|
|
60
|
-
let trailing_newlines = content.chars().rev().take_while(|&c| c == '\n').count();
|
|
61
|
-
let core = content.trim_matches('\n');
|
|
62
|
-
let is_whitespace_only = core.trim().is_empty();
|
|
63
|
-
|
|
64
|
-
let processed_content = if options.whitespace_mode == WhitespaceMode::Strict {
|
|
65
|
-
content
|
|
66
|
-
} else {
|
|
67
|
-
let mut core_text = if leading_newlines > 0 {
|
|
68
|
-
dedent_code_block(core)
|
|
69
|
-
} else {
|
|
70
|
-
core.to_string()
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
if is_whitespace_only {
|
|
74
|
-
let mut rebuilt = String::new();
|
|
75
|
-
for _ in 0..leading_newlines {
|
|
76
|
-
rebuilt.push('\n');
|
|
77
|
-
}
|
|
78
|
-
rebuilt.push_str(&core_text);
|
|
79
|
-
for _ in 0..trailing_newlines {
|
|
80
|
-
rebuilt.push('\n');
|
|
81
|
-
}
|
|
82
|
-
rebuilt
|
|
83
|
-
} else {
|
|
84
|
-
for _ in 0..trailing_newlines {
|
|
85
|
-
core_text.push('\n');
|
|
86
|
-
}
|
|
87
|
-
core_text
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
#[cfg(feature = "visitor")]
|
|
92
|
-
{
|
|
93
|
-
if let Some(ref visitor_handle) = ctx.visitor {
|
|
94
|
-
use crate::visitor::{NodeContext, NodeType, VisitResult};
|
|
95
|
-
|
|
96
|
-
if let Some(node) = node_handle.get(parser) {
|
|
97
|
-
if let tl::Node::Tag(tag) = node {
|
|
98
|
-
let attributes: BTreeMap<String, String> = collect_tag_attributes(tag);
|
|
99
|
-
|
|
100
|
-
let node_id = node_handle.get_inner();
|
|
101
|
-
let parent_tag = dom_ctx.parent_tag_name(node_id, parser);
|
|
102
|
-
let index_in_parent = dom_ctx.get_sibling_index(node_id).unwrap_or(0);
|
|
103
|
-
|
|
104
|
-
let node_ctx = NodeContext {
|
|
105
|
-
node_type: NodeType::Pre,
|
|
106
|
-
tag_name: "pre".to_string(),
|
|
107
|
-
attributes,
|
|
108
|
-
depth,
|
|
109
|
-
index_in_parent,
|
|
110
|
-
parent_tag,
|
|
111
|
-
is_inline: false,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
let visit_result = {
|
|
115
|
-
let mut visitor = visitor_handle.borrow_mut();
|
|
116
|
-
visitor.visit_code_block(&node_ctx, language.as_deref(), &processed_content)
|
|
117
|
-
};
|
|
118
|
-
match visit_result {
|
|
119
|
-
VisitResult::Continue => {
|
|
120
|
-
format_code_block(output, options, ctx, &processed_content, language.as_deref());
|
|
121
|
-
}
|
|
122
|
-
VisitResult::Custom(custom) => {
|
|
123
|
-
output.push_str(&custom);
|
|
124
|
-
}
|
|
125
|
-
VisitResult::Skip => {
|
|
126
|
-
// Skip code block
|
|
127
|
-
}
|
|
128
|
-
VisitResult::PreserveHtml => {
|
|
129
|
-
format_code_block(output, options, ctx, &processed_content, language.as_deref());
|
|
130
|
-
}
|
|
131
|
-
VisitResult::Error(err) => {
|
|
132
|
-
if ctx.visitor_error.borrow().is_none() {
|
|
133
|
-
*ctx.visitor_error.borrow_mut() = Some(err);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
format_code_block(output, options, ctx, &processed_content, language.as_deref());
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
#[cfg(not(feature = "visitor"))]
|
|
145
|
-
{
|
|
146
|
-
format_code_block(output, options, ctx, &processed_content, language.as_deref());
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/// Extract programming language from pre or nested code element's class attribute.
|
|
152
|
-
fn extract_language_from_pre(node_handle: &NodeHandle, parser: &Parser) -> Option<String> {
|
|
153
|
-
if let Some(node) = node_handle.get(parser) {
|
|
154
|
-
if let tl::Node::Tag(tag) = node {
|
|
155
|
-
// First, try to extract language from <pre> tag's class attribute
|
|
156
|
-
if let Some(class_attr) = tag.attributes().get("class") {
|
|
157
|
-
if let Some(class_bytes) = class_attr {
|
|
158
|
-
let class_str = class_bytes.as_utf8_str();
|
|
159
|
-
for cls in class_str.split_whitespace() {
|
|
160
|
-
if let Some(stripped) = cls.strip_prefix("language-") {
|
|
161
|
-
return Some(String::from(stripped));
|
|
162
|
-
} else if let Some(stripped) = cls.strip_prefix("lang-") {
|
|
163
|
-
return Some(String::from(stripped));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// If not found on <pre>, try to extract from nested <code> tag's class attribute
|
|
170
|
-
let children = tag.children();
|
|
171
|
-
for child_handle in children.top().iter() {
|
|
172
|
-
if let Some(tl::Node::Tag(child_tag)) = child_handle.get(parser) {
|
|
173
|
-
if child_tag.name() == "code" {
|
|
174
|
-
if let Some(class_attr) = child_tag.attributes().get("class") {
|
|
175
|
-
if let Some(class_bytes) = class_attr {
|
|
176
|
-
let class_str = class_bytes.as_utf8_str();
|
|
177
|
-
for cls in class_str.split_whitespace() {
|
|
178
|
-
if let Some(stripped) = cls.strip_prefix("language-") {
|
|
179
|
-
return Some(String::from(stripped));
|
|
180
|
-
} else if let Some(stripped) = cls.strip_prefix("lang-") {
|
|
181
|
-
return Some(String::from(stripped));
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
None
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/// Format code block with appropriate markdown syntax.
|
|
196
|
-
fn format_code_block(
|
|
197
|
-
output: &mut String,
|
|
198
|
-
options: &ConversionOptions,
|
|
199
|
-
ctx: &Context,
|
|
200
|
-
content: &str,
|
|
201
|
-
language: Option<&str>,
|
|
202
|
-
) {
|
|
203
|
-
match options.code_block_style {
|
|
204
|
-
CodeBlockStyle::Indented => {
|
|
205
|
-
if !ctx.convert_as_inline && !output.is_empty() && !output.ends_with("\n\n") {
|
|
206
|
-
if output.ends_with('\n') {
|
|
207
|
-
output.push('\n');
|
|
208
|
-
} else {
|
|
209
|
-
output.push_str("\n\n");
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
let indented = content
|
|
214
|
-
.lines()
|
|
215
|
-
.map(|line| {
|
|
216
|
-
if line.is_empty() {
|
|
217
|
-
String::new()
|
|
218
|
-
} else {
|
|
219
|
-
format!(" {line}")
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
.collect::<Vec<_>>()
|
|
223
|
-
.join("\n");
|
|
224
|
-
output.push_str(&indented);
|
|
225
|
-
|
|
226
|
-
output.push_str("\n\n");
|
|
227
|
-
}
|
|
228
|
-
CodeBlockStyle::Backticks | CodeBlockStyle::Tildes => {
|
|
229
|
-
if !ctx.convert_as_inline && !output.is_empty() && !output.ends_with("\n\n") {
|
|
230
|
-
if output.ends_with('\n') {
|
|
231
|
-
output.push('\n');
|
|
232
|
-
} else {
|
|
233
|
-
output.push_str("\n\n");
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
let fence = if options.code_block_style == CodeBlockStyle::Backticks {
|
|
238
|
-
"```"
|
|
239
|
-
} else {
|
|
240
|
-
"~~~"
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
output.push_str(fence);
|
|
244
|
-
if let Some(lang) = language {
|
|
245
|
-
output.push_str(lang);
|
|
246
|
-
} else if !options.code_language.is_empty() {
|
|
247
|
-
output.push_str(&options.code_language);
|
|
248
|
-
}
|
|
249
|
-
output.push('\n');
|
|
250
|
-
output.push_str(content);
|
|
251
|
-
output.push('\n');
|
|
252
|
-
output.push_str(fence);
|
|
253
|
-
output.push('\n');
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/// Dedent code block by finding the minimum indentation and removing it.
|
|
259
|
-
fn dedent_code_block(content: &str) -> String {
|
|
260
|
-
let lines: Vec<&str> = content.lines().collect();
|
|
261
|
-
if lines.is_empty() {
|
|
262
|
-
return String::new();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Find minimum indentation of non-empty lines
|
|
266
|
-
let min_indent = lines
|
|
267
|
-
.iter()
|
|
268
|
-
.filter(|line| !line.trim().is_empty())
|
|
269
|
-
.map(|line| line.chars().take_while(|c| c.is_whitespace()).count())
|
|
270
|
-
.min()
|
|
271
|
-
.unwrap_or(0);
|
|
272
|
-
|
|
273
|
-
let dedented: Vec<String> = lines
|
|
274
|
-
.iter()
|
|
275
|
-
.map(|line| {
|
|
276
|
-
if line.trim().is_empty() {
|
|
277
|
-
String::new()
|
|
278
|
-
} else {
|
|
279
|
-
let mut remaining = min_indent;
|
|
280
|
-
let mut cut = 0;
|
|
281
|
-
for (idx, ch) in line.char_indices() {
|
|
282
|
-
if remaining == 0 {
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
if ch.is_whitespace() {
|
|
286
|
-
remaining -= 1;
|
|
287
|
-
cut = idx + ch.len_utf8();
|
|
288
|
-
} else {
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
line[cut..].to_string()
|
|
293
|
-
}
|
|
294
|
-
})
|
|
295
|
-
.collect();
|
|
296
|
-
|
|
297
|
-
dedented.join("\n")
|
|
298
|
-
}
|