fix(html2md): keep preformatted blocks as markdown code

This commit is contained in:
lanyue-llk
2026-09-28 20:00:52 +08:00
parent 12675cdce9
commit 106c5ccf00
3 changed files with 52 additions and 21 deletions
+24
View File
@@ -37,6 +37,30 @@ pub(crate) fn contains<D: Dom + ?Sized>(
})
}
pub(crate) fn within<D: Dom + ?Sized>(
dom: &D,
root: D::NodeId,
limit: usize,
ids: &HashSet<String>,
) -> Vec<String> {
let mut found = Vec::new();
walk(dom, root, limit, |node| {
for attribute in ["id", "name"] {
if attribute == "name" && dom.node_kind(node) != NodeKind::Element("a") {
continue;
}
if let Some(id) = dom.attribute(node, attribute)
&& ids.contains(id)
&& !found.iter().any(|existing| existing == id)
{
found.push(id.to_owned());
}
}
false
});
found
}
fn walk<D: Dom + ?Sized>(
dom: &D,
root: D::NodeId,
+19 -13
View File
@@ -354,14 +354,15 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
}
"code" | "pre" => {
let remaining = self.options.max_depth - depth;
if subtree_has_link(self.dom, node, remaining)
|| (!self.anchor_targets.is_empty()
&& crate::anchors::contains(
self.dom,
node,
remaining,
&self.anchor_targets,
))
if tag == "code"
&& (subtree_has_link(self.dom, node, remaining)
|| (!self.anchor_targets.is_empty()
&& crate::anchors::contains(
self.dom,
node,
remaining,
&self.anchor_targets,
)))
{
let html = crate::html_table::render(
self.dom,
@@ -370,13 +371,18 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
self.options.max_depth,
&self.anchor_targets,
);
if tag == "pre" {
self.writer().block(html.into(), 2, 2);
} else {
self.writer().inline_html(&html);
}
self.writer().inline_html(&html);
return;
}
if tag == "pre" && !self.anchor_targets.is_empty() {
for id in
crate::anchors::within(self.dom, node, remaining, &self.anchor_targets)
{
if self.emitted_anchors.insert(id.clone()) {
self.writer().inline_html(&crate::anchors::markup(&id));
}
}
}
self.raw.clear();
let end = if tag == "pre" {
Task::EndPre(self.language(node))
+9 -8
View File
@@ -867,6 +867,8 @@ fn html_fallback_preserves_referenced_targets_for_special_nodes() {
false,
);
assert!(code.contains("<a id=\"L1\"></a>"), "{code}");
assert!(code.contains("```\nlet x = 1;\n```"), "{code}");
assert!(!code.contains("<pre>"), "{code}");
let table = markdown(
"<a href='#email'>Email</a><a href='#eq1'>Equation</a><a href='#video1'>Video</a><table><tr><td><input id='email' value='a@example.test'></td><td><math id='eq1'><mi>x</mi></math></td><td><video id='video1' src='/clip.mp4'></video></td></tr></table>",
false,
@@ -888,18 +890,15 @@ fn invalid_nested_and_empty_headings_do_not_emit_literal_markers() {
}
#[test]
fn preformatted_navigation_retains_link_targets() {
fn preformatted_navigation_retains_literal_content_as_markdown_code() {
let result = markdown(
"<pre>ALL GAMES: <a href='home.shtml'>HOME GAMES</a> : <a href='away.shtml'>AWAY GAMES</a>\nScores stay aligned</pre>",
false,
);
assert!(result.contains("<pre>"), "{result}");
assert!(result.starts_with("```\n"), "{result}");
assert!(!result.contains("<pre>"), "{result}");
assert!(
result.contains("<a href=\"home.shtml\">HOME GAMES</a>"),
"{result}"
);
assert!(
result.contains("<a href=\"away.shtml\">AWAY GAMES</a>"),
result.contains("ALL GAMES: HOME GAMES : AWAY GAMES"),
"{result}"
);
assert!(result.contains("Scores stay aligned"), "{result}");
@@ -912,10 +911,12 @@ fn preformatted_code_preserves_externally_referenced_targets() {
false,
);
assert!(result.contains("[Line 1](#L1)"), "{result}");
assert!(result.contains("<a id=\"L1\"></a>"), "{result}");
assert!(
result.contains("<span id=\"L1\">let x = 1;</span>"),
result.contains("```\nlet x = 1;\nlet y = 2;\n```"),
"{result}"
);
assert!(!result.contains("<pre>"), "{result}");
}
#[test]