diff --git a/moli-html2md/src/machine.rs b/moli-html2md/src/machine.rs index 990cda4c51..f488bde917 100644 --- a/moli-html2md/src/machine.rs +++ b/moli-html2md/src/machine.rs @@ -384,10 +384,10 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> { NodeKind::Text(text) => self.raw.push_str(text), // Inline code normalizes line endings to spaces. Keep explicit // breaks without converting descendant formatting into Markdown. - NodeKind::Element("br") if inline => self.raw.push('\n'), + NodeKind::Element("br") => self.raw.push('\n'), NodeKind::Document | NodeKind::Element(_) if depth + 1 < self.options.max_depth => { if !inline - && matches!(self.dom.node_kind(node), NodeKind::Element(tag) if is_block(tag)) + && matches!(self.dom.node_kind(node), NodeKind::Element(tag) if is_structural_block(tag)) { if !self.raw.is_empty() && !self.raw.ends_with('\n') { self.raw.push('\n'); @@ -449,17 +449,20 @@ fn class_language(class: Option<&str>) -> Option<&str> { } fn is_block(tag: &str) -> bool { + is_structural_block(tag) + || matches!( + tag, + "audio" | "canvas" | "frameset" | "isindex" | "noframes" | "output" + ) +} + +fn is_structural_block(tag: &str) -> bool { matches!( tag, "address" - | "audio" - | "canvas" - | "frameset" - | "isindex" - | "noframes" - | "output" | "article" | "aside" + | "blockquote" | "body" | "caption" | "center" @@ -475,14 +478,23 @@ fn is_block(tag: &str) -> bool { | "figure" | "footer" | "form" + | "h1" + | "h2" + | "h3" + | "h4" + | "h5" + | "h6" | "header" | "hgroup" | "html" | "legend" + | "li" | "main" | "menu" | "nav" + | "ol" | "p" + | "pre" | "section" | "summary" | "table" @@ -492,5 +504,6 @@ fn is_block(tag: &str) -> bool { | "th" | "thead" | "tr" + | "ul" ) } diff --git a/moli-html2md/src/writer.rs b/moli-html2md/src/writer.rs index 01b87e78ab..057e177e96 100644 --- a/moli-html2md/src/writer.rs +++ b/moli-html2md/src/writer.rs @@ -171,6 +171,11 @@ impl<'a> Writer<'a> { } pub(crate) fn code_with_edges(&mut self, text: &str, preformatted: bool) { + // An empty element has no visible edge. Keep the pending code until + // the next visible text or element decides whether it needs a gap. + if text.is_empty() { + return; + } // Each call represents a separate HTML code element. Keep adjacent // elements distinct even when the source has no whitespace between them. if self.code.is_some() { diff --git a/moli-html2md/tests/inline_boundaries.rs b/moli-html2md/tests/inline_boundaries.rs index a1a3f11ed5..f75981c774 100644 --- a/moli-html2md/tests/inline_boundaries.rs +++ b/moli-html2md/tests/inline_boundaries.rs @@ -106,6 +106,32 @@ fn pre_keeps_raw_text_and_line_endings() { ); } +#[test] +fn preformatted_examples_keep_inline_values_and_separate_display_lines() { + for (source, expected) in [ + ( + "
const answer = ;", + "const answer = 42;", + ), + ( + "
", + "TypeScript\nfunction identity() {}", + ), + ( + "TypeScript
function identity() {}
", + "first()\nsecond()", + ), + ("
- first()
- second()
first", "first\nsecond"), + ] { + let result = markdown(source); + assert_eq!( + rendered_html(&result), + format!("
second
{expected}\n\n"),
+ "{source}: {result}"
+ );
+ }
+}
+
#[test]
fn nested_emphasis_closes_with_markdown_before_a_word() {
for (html, expected, expected_html) in [
diff --git a/moli-html2md/tests/regressions.rs b/moli-html2md/tests/regressions.rs
index 7f8fc20c70..17ca740235 100644
--- a/moli-html2md/tests/regressions.rs
+++ b/moli-html2md/tests/regressions.rs
@@ -158,6 +158,21 @@ fn adjacent_code_elements_keep_distinct_values() {
);
}
+#[test]
+fn empty_code_between_values_does_not_join_or_invent_code_text() {
+ for empty in ["", ""] {
+ let source = format!("name{empty}string");
+ for preformatted in [false, true] {
+ let result = markdown(&source, preformatted);
+ assert_eq!(
+ rendered_html(&result),
+ "name string
";tsfunction identity() {}