From 106c5ccf009cac80da4d723a4e7176ce12e2ebef Mon Sep 17 00:00:00 2001 From: lanyue-llk <270302213+lanyue-llk@users.noreply.github.com> Date: Mon, 28 Sep 2026 13:34:05 +0800 Subject: [PATCH] fix(html2md): keep preformatted blocks as markdown code --- moli-html2md/src/anchors.rs | 24 +++++++++++++++++++++++ moli-html2md/src/machine.rs | 32 ++++++++++++++++++------------- moli-html2md/tests/regressions.rs | 17 ++++++++-------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/moli-html2md/src/anchors.rs b/moli-html2md/src/anchors.rs index 32c790dd26..ac97016687 100644 --- a/moli-html2md/src/anchors.rs +++ b/moli-html2md/src/anchors.rs @@ -37,6 +37,30 @@ pub(crate) fn contains( }) } +pub(crate) fn within( + dom: &D, + root: D::NodeId, + limit: usize, + ids: &HashSet, +) -> Vec { + 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( dom: &D, root: D::NodeId, diff --git a/moli-html2md/src/machine.rs b/moli-html2md/src/machine.rs index fe780200d0..914a0bccb7 100644 --- a/moli-html2md/src/machine.rs +++ b/moli-html2md/src/machine.rs @@ -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)) diff --git a/moli-html2md/tests/regressions.rs b/moli-html2md/tests/regressions.rs index 88bb7e28e2..e74808454e 100644 --- a/moli-html2md/tests/regressions.rs +++ b/moli-html2md/tests/regressions.rs @@ -867,6 +867,8 @@ fn html_fallback_preserves_referenced_targets_for_special_nodes() { false, ); assert!(code.contains(""), "{code}"); + assert!(code.contains("```\nlet x = 1;\n```"), "{code}"); + assert!(!code.contains("
"), "{code}");
     let table = markdown(
         "EmailEquationVideo
x
", 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( "
ALL GAMES: HOME GAMES : AWAY GAMES\nScores stay aligned
", false, ); - assert!(result.contains("
"), "{result}");
+    assert!(result.starts_with("```\n"), "{result}");
+    assert!(!result.contains("
"), "{result}");
     assert!(
-        result.contains("HOME GAMES"),
-        "{result}"
-    );
-    assert!(
-        result.contains("AWAY GAMES"),
+        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(""), "{result}");
     assert!(
-        result.contains("let x = 1;"),
+        result.contains("```\nlet x = 1;\nlet y = 2;\n```"),
         "{result}"
     );
+    assert!(!result.contains("
"), "{result}");
 }
 
 #[test]